1/* 2Donation Goal Countdown - Modified from flip clock 3Creates a dramatic countdown from donation goal to remaining amount 4*/ 5 6function flip(container, number, duration = 600) { 7 const currentNumber = container.dataset.number; 8 const baseTop = container.querySelector(".base .top"); 9 const baseBottom = container.querySelector(".base .bottom"); 10 const flaps = container.querySelectorAll(".flap"); 11 const front = container.querySelector(".front"); 12 const back = container.querySelector(".back"); 13 14 // Set the animation duration dynamically 15 container.style.setProperty("--flip-duration", `${duration}ms`); 16 17 // Update the container's data attribute to track current number 18 container.dataset.number = number; 19 20 // Set up the flip animation: front shows current, back shows new number 21 front.dataset.content = currentNumber; 22 back.dataset.content = number; 23 24 // Update the top base to show the new number immediately 25 baseTop.innerHTML = number; 26 27 // Trigger the flip animation by adding the show class 28 flaps.forEach((flap) => { 29 flap.classList.add("show"); 30 }); 31 32 // After animation completes, clean up and set final state 33 setTimeout(() => { 34 flaps.forEach((flap) => { 35 flap.classList.remove("show"); 36 }); 37 baseBottom.innerHTML = number; 38 }, duration); 39} 40 41const set = (container, number) => { 42 // Set number without animation - used for initial setup 43 const base = container.querySelectorAll(".base div"); 44 45 container.dataset.number = number; 46 base.forEach((div) => { 47 div.innerHTML = number; 48 }); 49}; 50 51// containerType = money, people, days 52const updateDisplay = (amount, shouldFlip, flipDuration = 600, containerType) => { 53 54 // Determine padding based on container type 55 let paddingLength; 56 if (containerType === "days") { 57 paddingLength = 2; 58 } else if (containerType === "people") { 59 paddingLength = 3; 60 } else { 61 paddingLength = 4; // default for money and other types 62 } 63 64 //console.log("updateDisplay"); 65 66 // Convert amount to string with appropriate padding 67 const amountString = String(amount).padStart(paddingLength, "0"); 68 69 // Get all digit container at once 70 const container = document.querySelectorAll(`.${containerType}-1, .${containerType}-2, .${containerType}-3, .${containerType}-4`); 71 72 //console.log("container", container); 73 74 // Update each digit position 75 container.forEach((container) => { 76 if (container instanceof HTMLElement && container) { 77 // Determine which digit this container should display based on its class 78 let digitIndex; 79 if (container.classList.contains(`${containerType}-1`)) digitIndex = 0; 80 else if (container.classList.contains(`${containerType}-2`)) digitIndex = 1; 81 else if (container.classList.contains(`${containerType}-3`)) digitIndex = 2; 82 else if (container.classList.contains(`${containerType}-4`)) digitIndex = 3; 83 84 const digit = amountString[digitIndex]; 85 const currentDigit = container.dataset.number; 86 87 //console.log("updateDisplay foreach"); 88 89 // Only update if the digit has actually changed and the digit exists 90 if (currentDigit !== digit && digit !== undefined) { 91 if (shouldFlip) { 92 //console.log("flipduration", flipDuration); 93 flip(container, digit, flipDuration); 94 } else { 95 //console.log("set to", digit); 96 set(container, digit); 97 } 98 } 99 } 100 }); 101}; 102 103// type = money, people, days 104const animateCountdown = (startAmount, endAmount, duration = 2000, type = "money") => { 105 const totalSteps = startAmount - endAmount; 106 const baseStepDuration = duration / totalSteps; 107 let currentAmount = startAmount; 108 109 //console.log("animationCountdown"); 110 111 // Set initial display without animation 112 updateDisplay(currentAmount, false, 1, type); 113 114 // Define how many steps from the end should start slowing down 115 const easeOutRange = Math.min(100, Math.floor(totalSteps * 0.3)); 116 const easeOutFactor = Math.pow(600, 1 / easeOutRange); 117 118 const scheduleNextStep = () => { 119 if (currentAmount <= endAmount) { 120 return; // We're done 121 } 122 123 currentAmount--; 124 const stepsRemaining = currentAmount - endAmount; 125 126 let targetStepDuration; 127 if (stepsRemaining <= easeOutRange) { 128 targetStepDuration = Math.max(baseStepDuration, Math.pow(easeOutFactor, easeOutRange - stepsRemaining)); 129 } else { 130 targetStepDuration = baseStepDuration; 131 } 132 133 updateDisplay(currentAmount, true, targetStepDuration, containerType = type); 134 135 // Schedule the next step with the guaranteed-safe duration 136 setTimeout(() => { 137 scheduleNextStep(); 138 }, targetStepDuration + 10); 139 }; 140 141 // Start the countdown chain 142 setTimeout(() => { 143 scheduleNextStep(); 144 }, baseStepDuration); 145}; 146
147function get_twingle_campaign_data(field) { 148 const root = document.querySelector('.'+field); 149 return root.textContent; 150} 151 152 153 154 155 156// Initialize the donation countdown when page loads 157document.addEventListener("DOMContentLoaded", function () { 158 // Start the dramatic countdown from goal to remaining amount 159 // This creates the effect of counting down from 3000 to whatever is still needed 160 // Configuration for your donation campaign 161 const DONATION_GOAL = 3000; 162 const CURRENT_DONATION_AMOUNT = get_twingle_campaign_data("data-current-donation-amount"); // This would come from your backend/API 163 const REMAINING_AMOUNT = DONATION_GOAL - CURRENT_DONATION_AMOUNT; 164 const TOTAL_DAYS = 30; 165 const start = new Date(get_twingle_campaign_data("data-first-day")); // Erster Tag 166 const today = new Date(); 167 const difference = today - start; 168 const REMAINING_DAYS = Math.ceil(30 - difference / (1000 * 60 * 60 * 24)); 169 // console.log("start:"+start); 170 // console.log("today:"+today); 171 // console.log("difference:"+difference); 172 // console.log("REMAINING_DAYS:"+REMAINING_DAYS); 173 const TOTAL_DONATORS = 300; 174 const CURRENT_DONATOR_COUNT = get_twingle_campaign_data("data-current-donator-count"); 175 const REMAINING_DONATOR_COUNT = TOTAL_DONATORS - CURRENT_DONATOR_COUNT; 176 const ANIMATION_DURATION = 3000; 177 // Todo trigger when visible ⦠178 animateCountdown(DONATION_GOAL, REMAINING_AMOUNT, ANIMATION_DURATION, "money"); 179 animateCountdown(TOTAL_DAYS, REMAINING_DAYS, ANIMATION_DURATION, "days"); 180 animateCountdown(TOTAL_DONATORS, REMAINING_DONATOR_COUNT, ANIMATION_DURATION, "people"); 181});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.