PageSourceSearch

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

js pupwife.com collected 2026-09-30 15:05:01 UTC 6,048 bytes, 197 lines download raw bytes

1/**
2 * CitrusOS theme transition — CRT scanlines + chromatic glitch (in-panel; taskbar stable).
3 * Visual patterns based on https://deloughry.co.uk/field-notes/building-glitch-effects-with-css/
4 */
5(function (global) {
6    'use strict';
7
8    var TOTAL_MS = 820;
9    var SWAP_MS = 410;
10    var VEIL_ID = 'citrusos-theme-glitch-veil';
11    var ROOT_CLASS = 'citrusos-theme-transition';
12    var HOST_CLASS = 'citrusos-theme-glitch-host';
13    var OVERLAY_CLASS = 'citrusos-theme-glitch-overlay';
14    var BLOCK_CLASS = 'citrusos-glitch-block';
15    var BLOCK_COUNT = 6;
16    var running = false;
17    var activeTimers = [];
18
19    var LAYER_BASE = {
20        desktop: 0,
21        widgets: 95,
22        windows: 190
23    };
24
25    function prefersReducedMotion() {
26        try {
27            return global.matchMedia && global.matchMedia('(prefers-reduced-motion: reduce)').matches;
28        } catch (e) {
29            return false;
30        }
31    }
32
33    function clearTimers() {
34        for (var i = 0; i < activeTimers.length; i++) {
35            global.clearTimeout(activeTimers[i]);
36        }
37        activeTimers = [];
38    }
39
40    function schedule(fn, ms) {
41        var id = global.setTimeout(fn, ms);
42        activeTimers.push(id);
43        return id;
44    }
45
46    function ensureVeil() {
47        var veil = document.getElementById(VEIL_ID);
48        if (veil) return veil;
49
50        veil = document.createElement('div');
51        veil.id = VEIL_ID;
52        veil.className = 'citrusos-theme-glitch-veil';
53        veil.setAttribute('aria-hidden', 'true');
54        document.body.appendChild(veil);
55        return veil;
56    }
57
58    function createOverlay(options) {
59        options = options || {};
60        var overlay = document.createElement('div');
61        overlay.className = OVERLAY_CLASS;
62        overlay.setAttribute('aria-hidden', 'true');
63
64        if (options.desktop) {
65            overlay.classList.add(OVERLAY_CLASS + '--desktop');
66        } else {
67            for (var b = 0; b < BLOCK_COUNT; b++) {
68                var block = document.createElement('span');
69                block.className = BLOCK_CLASS + ' ' + BLOCK_CLASS + '--' + (b + 1);
70                overlay.appendChild(block);
71            }
72        }
73
74        return overlay;
75    }
76
77    function mountGlitchHost(host, delayMs, bucket, options) {
78        if (!host) return;
79
80        host.classList.add(HOST_CLASS);
81        if (options && options.desktop) {
82            host.classList.add(HOST_CLASS + '--desktop');
83        }
84        host.style.setProperty('--citrusos-theme-glitch-delay', delayMs + 'ms');
85
86        var overlay = createOverlay(options);
87        host.appendChild(overlay);
88
89        bucket.push({
90            host: host,
91            overlay: overlay
92        });
93    }
94
95    function mountDesktopGlitches(bucket) {
96        mountGlitchHost(
97            document.getElementById('citrusos-desktop'),
98            LAYER_BASE.desktop,
99            bucket,
100            { desktop: true }
101        );
102    }
103
104    function mountWidgetGlitches(bucket) {
105        var widgets = document.querySelectorAll('#citrusos-widgets .citrus-widget__body');
106        for (var i = 0; i < widgets.length; i++) {
107            mountGlitchHost(widgets[i], LAYER_BASE.widgets + i * 36, bucket);
108        }
109    }
110
111    function mountWindowGlitches(bucket) {
112        var bodies = document.querySelectorAll('#citrusos-windows .citrus-window__body');
113        for (var i = 0; i < bodies.length; i++) {
114            mountGlitchHost(bodies[i], LAYER_BASE.windows + i * 42, bucket);
115        }
116
117        var fullscreen = document.querySelector('#citrusos-fullscreen:not([hidden]) .citrus-fullscreen__content');
118        if (fullscreen) {
119            mountGlitchHost(fullscreen, LAYER_BASE.windows + bodies.length * 42, bucket);
120        }
121    }
122
123    function collectGlitchMounts() {
124        var mounted = [];
125        mountDesktopGlitches(mounted);
126        mountWidgetGlitches(mounted);
127        mountWindowGlitches(mounted);
128        return mounted;
129    }
130
131    function clearGlitchMounts(mounted) {
132        for (var i = 0; i < mounted.length; i++) {
133            var entry = mounted[i];
134            if (!entry || !entry.host) continue;
135
136            entry.host.classList.remove(HOST_CLASS);
137            entry.host.classList.remove(HOST_CLASS + '--desktop');
138            entry.host.style.removeProperty('--citrusos-theme-glitch-delay');
139
140            if (entry.overlay && entry.overlay.parentNode) {
141                entry.overlay.parentNode.removeChild(entry.overlay);
142            }
143        }
144    }
145
146    function finishTransition(root, veil, mounted) {
147        if (veil) veil.classList.remove('is-active');
148        clearGlitchMounts(mounted);
149        if (root) root.classList.remove(ROOT_CLASS);
150        running = false;
151        clearTimers();
152    }
153
154    function play(theme, applyFn) {
155        if (typeof applyFn !== 'function') return;
156
157        var body = document.body;
158        if (!body || !body.classList.contains('page-citrusos')) {
159            applyFn();
160            return;
161        }
162
163        if (prefersReducedMotion() || running) {
164            applyFn();
165            return;
166        }
167
168        running = true;
169        clearTimers();
170
171        var root = document.documentElement;
172        var veil = ensureVeil();
173        var mounted = collectGlitchMounts();
174
175        root.classList.add(ROOT_CLASS);
176        veil.classList.add('is-active');
177
178        schedule(function () {
179            try {
180                applyFn();
181            } catch (e) {
182                finishTransition(root, veil, mounted);
183                throw e;
184            }
185        }, SWAP_MS);
186
187        schedule(function () {
188            finishTransition(root, veil, mounted);
189        }, TOTAL_MS);
190    }
191
192    global.CitrusThemeTransition = {
193        play: play,
194        TOTAL_MS: TOTAL_MS,
195        SWAP_MS: SWAP_MS
196    };
197})(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.