PageSourceSearch

https://pupwife.com/scripts/desktop/citrusBoot.js

js pupwife.com collected 2026-09-30 15:04:50 UTC 13,098 bytes, 374 lines download raw bytes

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.