PageSourceSearch

https://lab-rho-opal.vercel.app/unjobber/public/scripts/app.js

js lab-rho-opal.vercel.app collected 2026-10-03 07:12:43 UTC 7,087 bytes, 233 lines download raw bytes

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.