1let currentScreen = 'screen-intro'; 2 3let processMessageTimeout; 4let ellipsisInterval; 5 6function showScreen(screenId) { 7 const sparkle = document.getElementById('shared-sparkle'); 8 const iconHolder = document.querySelector('.icon-holder'); 9 10 // Hide sparkle on intro; show on other screens 11 if (screenId === 'screen-intro') { 12 document.body.classList.add('hide-sparkle'); 13 } else { 14 document.body.classList.remove('hide-sparkle'); 15 // Clear any shrink classes when not going to results 16 if (sparkle) sparkle.classList.remove('shrink-sparkle'); 17 if (iconHolder) iconHolder.classList.remove('shrink-holder'); 18 } 19 20 // Toggle grow-sparkle class on thinking screen, remove otherwise 21 if (screenId === 'screen-thinking' && sparkle) { 22 sparkle.classList.add('grow-sparkle'); 23 iconHolder.classList.add('grow-holder'); 24 } else if (sparkle) { 25 sparkle.classList.remove('grow-sparkle'); 26 iconHolder.classList.remove('grow-holder'); 27 } 28 29 // If transitioning from thinking â results, animate shrink first 30 if ( 31 currentScreen === 'screen-thinking' && 32 screenId === 'screen-results' && 33 sparkle && 34 iconHolder 35 ) { 36 sparkle.classList.add('shrink-sparkle'); 37 iconHolder.classList.add('shrink-holder'); 38 setTimeout(() => { 39 actuallyShowScreenContent(screenId); 40 }, 500); 41 } else { 42 actuallyShowScreenContent(screenId); 43 } 44 45 currentScreen = screenId; 46} 47 48function actuallyShowScreenContent(screenId) { 49 // Toggle back button visibility based on screen 50 const backBtn = document.getElementById('back-button'); 51 if (backBtn) { 52 backBtn.style.display = (screenId === 'screen-results') ? 'block' : 'none'; 53 } 54 55 // Hide all currently visible elements 56 document.querySelectorAll('.screen-element.visible').forEach(el => { 57 el.classList.remove('visible'); 58 //el.style.display = 'none'; 59 }); 60 61 // Show the requested screen's elements 62 const container = document.getElementById(screenId); 63 if (!container) return; 64 65 // For each .screen-element inside the container, fade in and display it 66 container.querySelectorAll('.screen-element').forEach(el => { 67 // Allow the browser one repaint before applying display:flex and fade in 68 requestAnimationFrame(() => { 69 el.classList.add('visible'); 70 }); 71 }); 72 73 if (screenId === 'screen-results') { 74 requestAnimationFrame(() => { 75 setTimeout(() => { 76 const doomPercentEl = document.getElementById("doom-percent"); 77 const doomBarFill = document.querySelector(".doom-bar .fill"); 78 79 if (doomPercentEl && doomBarFill) { 80 const percent = parseInt(doomPercentEl.textContent); 81 doomBarFill.style.transition = "none"; 82 doomBarFill.style.width = "0%"; 83 void doomBarFill.offsetWidth; 84 doomBarFill.style.transition = "width 1.2s ease-out"; 85 doomBarFill.style.width = percent + "%"; 86 87 let start = 0; 88 const duration = 1200; 89 const startTime = performance.now(); 90 91 function animateCount(currentTime) { 92 const elapsed = currentTime - startTime; 93 const progress = Math.min(elapsed / duration, 1); 94 const current = Math.floor(progress * percent); 95 doomPercentEl.textContent = current + "%"; 96 if (progress < 1) { 97 requestAnimationFrame(animateCount); 98 } 99 } 100 requestAnimationFrame(animateCount); 101 } 102 }, 0); 103 }); 104 } 105} 106 107 108function startProcessMessages() { 109 const pmDiv = document.getElementById('process-message'); 110 if (!pmDiv) return; 111 112 const messages = [ 113 "Pretending to think", 114 "Calculating your irrelevance", 115 "Analyzing doom", 116 "Scanning for hope", 117 "Searching for purpose", 118 "Evaluating obsolescence", 119 "Processing livelihood", 120 "Minimizing income potential", 121 "Optimizing despair", 122 "Reducing expectations", 123 ]; 124 125 let baseMessage = ""; 126 let dotCount = 0; 127 128 function showRandomMessage() { 129 baseMessage = messages[Math.floor(Math.random() * messages.length)]; 130 dotCount = 1; 131 } 132 133 // Show initial random message
134 showRandomMessage(); 135 136 // Schedule subsequent messages at random 1-3 second intervals 137 function scheduleNext() { 138 const delay = 800 + Math.random() * 1800; 139 processMessageTimeout = setTimeout(() => { 140 showRandomMessage(); 141 scheduleNext(); 142 }, delay); 143 } 144 scheduleNext(); 145 146 // Animate ellipses every 500ms 147 ellipsisInterval = setInterval(() => { 148 dotCount = (dotCount + 1) % 4; // cycles through 0-3 dots 149 pmDiv.textContent = baseMessage + ".".repeat(dotCount); 150 }, 300); 151} 152 153function stopProcessMessages() { 154 const pmDiv = document.getElementById('process-message'); 155 if (!pmDiv) return; 156 clearTimeout(processMessageTimeout); 157 clearInterval(ellipsisInterval); 158 pmDiv.textContent = ""; 159} 160 161async function checkJob() { 162 const job = document.getElementById("jobInput").value.trim(); 163 if (!job) return; 164 165 showScreen('screen-thinking'); // Show thinking screen 166 startProcessMessages(); 167 168 try { 169 const response = await fetch("/api/job-check", { 170 method: "POST", 171 headers: { "Content-Type": "application/json" }, 172 body: JSON.stringify({ job }) 173 }); 174 175 const data = await response.json(); 176 177 if (!data.doomScore || !data.prediction) { 178 throw new Error("Incomplete response from server."); 179 } 180 181 const doomScore = Math.min(Math.max(data.doomScore, 0), 100); 182 183 // Populate result screen 184 document.getElementById("job-title").textContent = data.jobTitle; 185 document.getElementById("doom-percent").textContent = `${doomScore}%`; 186 //document.querySelector(".doom-bar .fill").style.width = `${doomScore}%`; 187 document.getElementById("summary").textContent = data.prediction; 188 189 const replacementsList = document.getElementById("replacements"); 190 replacementsList.innerHTML = ""; 191 data.replacements?.forEach(item => { 192 const li = document.createElement("li"); 193 const link = document.createElement("a"); 194 link.href = item.url; 195 link.target = "_blank"; 196 link.textContent = item.name; 197 li.appendChild(link); 198 replacementsList.appendChild(li); 199 }); 200 201 document.getElementById("suggestions").textContent = data.brightSide; 202 203 stopProcessMessages(); 204 showScreen('screen-results'); // Show results screen 205 206 } catch (err) { 207 console.error(err); 208 alert("â ï¸ Error checking job. Try again."); 209 stopProcessMessages(); 210 showScreen('screen-entry'); 211 } 212} 213 214document.addEventListener('DOMContentLoaded', () => { 215 showScreen('screen-intro'); 216 const startBtn = document.getElementById('start-button'); 217 if (startBtn) { 218 startBtn.addEventListener('click', () => { 219 showScreen('screen-entry'); 220 }); 221 } 222 const backBtn = document.getElementById('back-button'); 223 if (backBtn) { 224 backBtn.addEventListener('click', () => { 225 document.getElementById("jobInput").value = ""; 226 showScreen('screen-entry'); 227 }); 228 } 229 document.querySelector("#jobForm")?.addEventListener("submit", function(e) { 230 e.preventDefault(); 231 checkJob(); 232 }); 233});
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.