1/** 2 * CitrusOS retro boot sequence â progress bar tracks real boot milestones. 3 */ 4(function (global) { 5 'use strict'; 6 7 var FADE_MS = 420; 8 var BOOT_DEP_TIMEOUT_MS = 15000; 9 var LOGO_SRC = '/images/CitrusOS/CitrusOS_logo.png'; 10 var MUSIC_PRELOAD_TIMEOUT_MS = 120000; 11 var BIOS_MAX_VISIBLE_LINES = 4; 12 13 /** Parallel boot tracks â overall bar = weighted sum of track progress. */ 14 var BOOT_TRACKS = { 15 ipban: { weight: 0.07 }, 16 shell: { weight: 0.20 }, 17 services: { weight: 0.22 }, 18 ambient: { weight: 0.30 }, 19 music: { weight: 0.13 }, 20 finalize: { weight: 0.08 } 21 }; 22 23 var progress = { 24 fill: null, 25 pre: null, 26 reduced: false, 27 target: 0, 28 complete: false, 29 activityLineActive: false, 30 allLines: [], 31 tracks: Object.create(null) 32 }; 33 34 function renderBiosLog() { 35 if (!progress.pre) return; 36 var lines = progress.allLines; 37 var start = Math.max(0, lines.length - BIOS_MAX_VISIBLE_LINES); 38 progress.pre.textContent = lines.slice(start).join('\n'); 39 } 40 41 function appendBiosLine(text) { 42 if (!text) return; 43 progress.allLines.push(text); 44 renderBiosLog(); 45 } 46 47 function prefersReducedMotion() { 48 return global.matchMedia && global.matchMedia('(prefers-reduced-motion: reduce)').matches; 49 } 50 51 function ensureBootingClass() { 52 document.documentElement.classList.add('citrusos-booting'); 53 } 54 55 function applyFill(ratio) { 56 if (!progress.fill) return; 57 var pct = (Math.max(0, Math.min(1, ratio)) * 100).toFixed(1) + '%'; 58 if (progress.reduced) { 59 progress.fill.style.transition = 'none'; 60 } else { 61 progress.fill.style.transition = 'width 0.42s ease-out'; 62 } 63 progress.fill.style.width = pct; 64 } 65 66 function getBootHeaderLine() { 67 if (global.CitrusBirthdayConfig && typeof global.CitrusBirthdayConfig.getBootBiosLines === 'function') { 68 var partyLines = global.CitrusBirthdayConfig.getBootBiosLines(); 69 if (partyLines && partyLines.length) return partyLines[0]; 70 } 71 return 'CitrusOS BIOS v0.1 â pupwife.com'; 72 } 73 74 function initTrackState() { 75 progress.tracks = Object.create(null); 76 var key; 77 for (key in BOOT_TRACKS) { 78 if (Object.prototype.hasOwnProperty.call(BOOT_TRACKS, key)) { 79 progress.tracks[key] = 0; 80 } 81 } 82 } 83 84 function recomputeBootProgress() { 85 var sum = 0; 86 var weightTotal = 0; 87 var key; 88 for (key in BOOT_TRACKS) { 89 if (!Object.prototype.hasOwnProperty.call(BOOT_TRACKS, key)) continue; 90 var w = BOOT_TRACKS[key].weight; 91 weightTotal += w; 92 sum += w * (progress.tracks[key] || 0); 93 } 94 var ratio = weightTotal ? sum / weightTotal : 0; 95 progress.target = Math.max(progress.target, Math.min(progress.complete ? 1 : 0.99, ratio)); 96 applyFill(progress.target); 97 } 98 99 function setBiosActivity(text) { 100 if (!progress.pre || progress.complete || !text) return; 101 if (!progress.activityLineActive) { 102 appendBiosLine(text); 103 progress.activityLineActive = true; 104 } else if (progress.allLines.length) { 105 progress.allLines[progress.allLines.length - 1] = text; 106 renderBiosLog(); 107 } 108 } 109 110 function finalizeBiosActivity(text) { 111 setBiosActivity(text); 112 progress.activityLineActive = false; 113 } 114 115 function reportBoot(track, fraction, label) { 116 if (progress.complete || !BOOT_TRACKS[track]) return; 117 var next = Math.max(progress.tracks[track] || 0, Math.min(1, fraction)); 118 progress.tracks[track] = next; 119 if (label) setBiosActivity(label); 120 recomputeBootProgress(); 121 } 122 123 function finalizeBootTrack(track, label) { 124 if (!BOOT_TRACKS[track]) return; 125 progress.tracks[track] = 1; 126 if (label) finalizeBiosActivity(label); 127 recomputeBootProgress(); 128 } 129 130 /** @deprecated Prefer reportBoot / finalizeBootTrack for accurate boot phases. */
131 function setProgress(ratio) { 132 if (progress.complete) return; 133 var next = Math.max(progress.target, Math.min(0.99, ratio)); 134 if (next <= progress.target) return; 135 progress.target = next; 136 applyFill(progress.target); 137 } 138 139 function maxLineLength(text) { 140 if (!text) return 0; 141 var lines = text.split('\n'); 142 var max = 0; 143 var i; 144 for (i = 0; i < lines.length; i++) { 145 if (lines[i].length > max) max = lines[i].length; 146 } 147 return max; 148 } 149 150 function pickBootAsciiArt() { 151 var variants = global.CitrusBootAsciiVariants; 152 if (!variants || !variants.length) return ''; 153 var birthdayArt = global.CitrusBootAsciiVariantsBirthday; 154 if (birthdayArt && global.CitrusBirthdayConfig && 155 global.CitrusBirthdayConfig.isBirthdayActive && 156 global.CitrusBirthdayConfig.isBirthdayActive() && 157 Math.random() < 0.72) { 158 return birthdayArt; 159 } 160 var idx = Math.floor(Math.random() * variants.length); 161 return variants[idx]; 162 } 163 164 function applyBootAsciiHeader(pre) { 165 if (!pre) return; 166 var art = pickBootAsciiArt(); 167 pre.textContent = art; 168 pre.style.fontSize = ''; 169 if (!art) return; 170 var maxLen = maxLineLength(art); 171 if (maxLen <= 0) return; 172 var targetPx = Math.min(560, global.innerWidth * 0.88) - 48; 173 var sizePx = Math.max(8.5, Math.min(13.5, (targetPx / maxLen) * 1.08)); 174 pre.style.fontSize = sizePx + 'px'; 175 } 176 177 function initProgress(fill, pre, reduced) { 178 progress.fill = fill || null; 179 progress.pre = pre || null; 180 progress.reduced = !!reduced; 181 progress.target = 0; 182 progress.complete = false; 183 progress.activityLineActive = false; 184 progress.allLines = []; 185 initTrackState(); 186 if (progress.fill) { 187 progress.fill.style.width = '0%'; 188 progress.fill.style.transition = 'none'; 189 } 190 if (progress.pre) { 191 progress.pre.textContent = ''; 192 progress.pre.style.fontSize = ''; 193 } 194 } 195 196 function kickIpBanBoot() { 197 var path = (document.location.pathname || '').replace(/\\/g, '/'); 198 if (/\/(fuckoff|banned|admin|hashgenerator)\.html$/i.test(path)) return; 199 200 reportBoot('ipban', 0, 'Checking access policy...'); 201 202 function startBanGate() { 203 if (global.PupwifeIpBan && typeof global.PupwifeIpBan.start === 'function') { 204 global.PupwifeIpBan.start(); 205 } 206 } 207 208 if (global.__PUPWIFE_IP_BAN_BOOTED__) { 209 startBanGate(); 210 return; 211 } 212 213 global.__PUPWIFE_IP_BAN_PENDING_START__ = true; 214 var script = document.createElement('script'); 215 script.src = '/scripts/dependencies/ipBanBoot.js'; 216 script.onload = startBanGate; 217 script.onerror = function () { 218 global.__PUPWIFE_IP_BAN_PENDING_START__ = false; 219 }; 220 (document.head || document.documentElement).appendChild(script); 221 } 222 223 function waitForIpBanReady() { 224 if (global.PupwifeIpBan && typeof global.PupwifeIpBan.whenReady === 'function') { 225 return global.PupwifeIpBan.whenReady().then(function () { 226 finalizeBootTrack('ipban', 'Access policy... OK'); 227 }); 228 } 229 finalizeBootTrack('ipban', 'Access policy... OK'); 230 return Promise.resolve(); 231 } 232 233 function waitForShellReady() { 234 if (global.__citrusShellReady) { 235 finalizeBootTrack('shell', 'Shell modules... OK'); 236 finalizeBootTrack('services', 'Global services... OK'); 237 finalizeBootTrack('ambient', 'Ambient effects... OK'); 238 finalizeBootTrack('finalize', 'Shell ready... OK'); 239 return Promise.resolve(); 240 } 241 return new Promise(function (resolve) { 242 var timeout = global.setTimeout(function () { 243 finalizeBootTrack('shell', 'Shell modules... OK'); 244 finalizeBootTrack('services', 'Global services... OK'); 245 finalizeBootTrack('ambient', 'Ambient effects... OK'); 246 finalizeBootTrack('finalize', 'Shell ready... OK'); 247 resolve(); 248 }, BOOT_DEP_TIMEOUT_MS); 249 global.addEventListener('pupwife:citrus-shell-ready', function () { 250 global.clearTimeout(timeout); 251 resolve(); 252 }, { once: true }); 253 }); 254 } 255 256 function waitForMusicPreload() { 257 if (global.MusicPagePreload && typeof global.MusicPagePreload.isLocalComplete === 'function' && global.MusicPagePreload.isLocalComplete()) { 258 return Promise.resolve(); 259 } 260 261 reportBoot('music', 0, 'Preparing music library...'); 262 263 return new Promise(function (resolve) { 264 var done = false;
265 function finish() { 266 if (done) return; 267 done = true; 268 global.removeEventListener('musicpage:preloadprogress', onProgress); 269 resolve(); 270 } 271 272 function onProgress(ev) { 273 var d = ev && ev.detail; 274 if (!d || !d.label) return; 275 setBiosActivity(d.label); 276 if (d.trackFraction != null && global.CitrusBoot && typeof global.CitrusBoot.reportBoot === 'function') { 277 global.CitrusBoot.reportBoot('music', d.trackFraction, d.label); 278 } 279 } 280 281 global.addEventListener('musicpage:preloadprogress', onProgress); 282 283 var timeout = global.setTimeout(finish, MUSIC_PRELOAD_TIMEOUT_MS); 284 global.addEventListener('musicpage:localpreloadcomplete', function () { 285 global.clearTimeout(timeout); 286 finish(); 287 }, { once: true }); 288 289 if (global.MusicPagePreload && typeof global.MusicPagePreload.ensureStarted === 'function') { 290 global.MusicPagePreload.ensureStarted(); 291 } else if (global.MusicPagePreload && typeof global.MusicPagePreload.start === 'function') { 292 global.MusicPagePreload.start(); 293 } 294 295 if (global.MusicPagePreload && typeof global.MusicPagePreload.isLocalComplete === 'function' && global.MusicPagePreload.isLocalComplete()) { 296 global.clearTimeout(timeout); 297 finish(); 298 } 299 }); 300 } 301 302 function finishBoot(boot, onComplete) { 303 if (!boot) { 304 document.documentElement.classList.remove('citrusos-booting'); 305 if (onComplete) onComplete(); 306 return; 307 } 308 309 boot.classList.add('citrusos-boot--done'); 310 311 global.setTimeout(function () { 312 boot.hidden = true; 313 document.documentElement.classList.remove('citrusos-booting'); 314 if (onComplete) onComplete(); 315 }, FADE_MS); 316 } 317 318 function preloadBootLogo() { 319 var img = new Image(); 320 img.src = LOGO_SRC; 321 } 322 323 function start(onComplete) { 324 var boot = document.getElementById('citrusos-boot'); 325 var fill = boot && boot.querySelector('.citrusos-boot__fill'); 326 var logPre = boot && boot.querySelector('.citrusos-boot__log'); 327 var asciiPre = boot && boot.querySelector('.citrusos-boot__ascii'); 328 var reduced = prefersReducedMotion(); 329 330 if (!boot) { 331 document.documentElement.classList.remove('citrusos-booting'); 332 if (onComplete) onComplete(); 333 return; 334 } 335 336 ensureBootingClass(); 337 initProgress(fill, logPre, reduced); 338 applyBootAsciiHeader(asciiPre); 339 preloadBootLogo(); 340 appendBiosLine(getBootHeaderLine()); 341 reportBoot('shell', 0, 'Loading shell modules...'); 342 kickIpBanBoot(); 343 344 Promise.all([ 345 waitForIpBanReady(), 346 waitForShellReady(), 347 waitForMusicPreload() 348 ]).then(function () { 349 var key; 350 for (key in BOOT_TRACKS) { 351 if (Object.prototype.hasOwnProperty.call(BOOT_TRACKS, key)) { 352 progress.tracks[key] = 1; 353 } 354 } 355 progress.complete = true; 356 progress.target = 1; 357 finalizeBiosActivity('Boot complete... OK'); 358 applyFill(1); 359 global.setTimeout(function () { 360 finishBoot(boot, onComplete); 361 }, reduced ? 0 : 340); 362 }); 363 } 364 365 global.CitrusBoot = { 366 start: start, 367 setProgress: setProgress, 368 reportBoot: reportBoot, 369 finalizeBootTrack: finalizeBootTrack, 370 setBiosActivity: setBiosActivity, 371 finalizeBiosActivity: finalizeBiosActivity, 372 LOGO_SRC: LOGO_SRC 373 }; 374})(typeof window !== 'undefined' ? window : this);
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.