PageSourceSearch

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

js pupwife.com collected 2026-09-30 15:04:05 UTC 5,939 bytes, 197 lines download raw bytes

1/**
2 * CitrusOS browser-zoom compensation — keep shell chrome and wallpaper at a stable
3 * visual size when the visitor uses Ctrl+scroll or pinch zoom.
4 *
5 * Desktop Ctrl+/- does NOT update visualViewport.scale (pinch only). Page zoom is
6 * tracked from devicePixelRatio + innerWidth moving together.
7 */
8(function (global) {
9    'use strict';
10
11    if (global.CitrusBrowserZoom) return;
12
13    var pageZoom = 1;
14    var baselineInnerWidth = 0;
15    var baselineDpr = 0;
16    var rafId = 0;
17    var initialized = false;
18    var dprMedia = null;
19
20    function isCitrusPage() {
21        return document.body && document.body.classList.contains('page-citrusos');
22    }
23
24    function clampScale(scale) {
25        if (!Number.isFinite(scale) || scale <= 0) return 1;
26        return Math.min(5, Math.max(0.25, scale));
27    }
28
29    function recalibrateBaseline() {
30        baselineInnerWidth = global.innerWidth;
31        baselineDpr = global.devicePixelRatio || 1;
32        pageZoom = 1;
33    }
34
35    /**
36     * @returns {number} browser/page zoom factor (1 = 100%)
37     */
38    function readPageZoom() {
39        var innerWidth = global.innerWidth;
40        var dpr = global.devicePixelRatio || 1;
41        var pinch = global.visualViewport && typeof global.visualViewport.scale === 'number'
42            ? global.visualViewport.scale
43            : 1;
44
45        if (Math.abs(pinch - 1) > 0.001) {
46            baselineInnerWidth = innerWidth;
47            baselineDpr = dpr;
48            pageZoom = clampScale(pinch);
49            return pageZoom;
50        }
51
52        if (!baselineInnerWidth) {
53            recalibrateBaseline();
54            return 1;
55        }
56
57        var zoomByWidth = baselineInnerWidth / innerWidth;
58        var zoomByDpr = dpr / baselineDpr;
59
60        if (Math.abs(zoomByWidth - zoomByDpr) < 0.05) {
61            pageZoom = clampScale(zoomByWidth);
62            if (Math.abs(pageZoom - 1) < 0.02) {
63                recalibrateBaseline();
64                return 1;
65            }
66            return pageZoom;
67        }
68
69        if (Math.abs(zoomByDpr - 1) < 0.01) {
70            baselineInnerWidth = innerWidth;
71            baselineDpr = dpr;
72            return pageZoom;
73        }
74
75        recalibrateBaseline();
76        return 1;
77    }
78
79    function isCompensating(scale) {
80        return Math.abs(scale - 1) > 0.001;
81    }
82
83    function apply() {
84        if (!isCitrusPage()) return;
85
86        var prevScale = pageZoom;
87        var scale = readPageZoom();
88        var changed = Math.abs(scale - prevScale) > 0.001;
89        var root = document.documentElement;
90        var compensate = 1 / scale;
91
92        root.style.setProperty('--citrusos-browser-zoom', String(scale));
93        root.style.setProperty('--citrusos-zoom-compensate', String(compensate));
94        document.body.classList.toggle('citrusos-browser-zoom-active', isCompensating(scale));
95
96        if (!changed) return;
97
98        try {
99            global.dispatchEvent(new CustomEvent('pupwife:citrus-browser-zoom', {
100                detail: { scale: scale, compensate: compensate }
101            }));
102        } catch (e) { /* ignore */ }
103        try {
104            global.dispatchEvent(new Event('resize'));
105        } catch (err) { /* ignore */ }
106    }
107
108    function scheduleApply() {
109        if (rafId) return;
110        rafId = global.requestAnimationFrame(function () {
111            rafId = 0;
112            apply();
113        });
114    }
115
116    function onWheel(ev) {
117        if (!ev.ctrlKey && !ev.metaKey) return;
118        scheduleApply();
119    }
120
121    function onKeyDown(ev) {
122        if (!ev.ctrlKey && !ev.metaKey) return;
123        if (ev.key === '0' || ev.key === '=' || ev.key === '+' || ev.key === '-' || ev.key === '_') {
124            scheduleApply();
125        }
126    }
127
128    function bindDprWatcher() {
129        function attach() {
130            if (dprMedia) {
131                dprMedia.removeEventListener('change', onDprMediaChange);
132            }
133            var query = '(resolution: ' + (global.devicePixelRatio || 1) + 'dppx)';
134            dprMedia = global.matchMedia(query);
135            dprMedia.addEventListener('change', onDprMediaChange);
136        }
137
138        function onDprMediaChange() {
139            scheduleApply();
140            attach();
141        }
142
143        attach();
144    }
145
146    function ensureZoomLayerClasses() {
147        ['page-background', 'aurora-overlay', 'webgl-overlay'].forEach(function (id) {
148            var el = document.getElementById(id);
149            if (!el) return;
150            el.classList.add('citrusos-browser-zoom-layer', 'citrusos-browser-zoom-layer--viewport');
151        });
152    }
153
154    function bind() {
155        if (initialized || !isCitrusPage()) return;
156        initialized = true;
157        ensureZoomLayerClasses();
158        apply();
159        global.addEventListener('resize', scheduleApply, { passive: true });
160        global.addEventListener('wheel', onWheel, { passive: true });
161        global.addEventListener('keydown', onKeyDown);
162        if (global.visualViewport) {
163            global.visualViewport.addEventListener('resize', scheduleApply);
164        }
165        bindDprWatcher();
166    }
167
168    function toLogicalDelta(dx, dy) {
169        if (!isCompensating(pageZoom)) {
170            return { dx: dx, dy: dy };
171        }
172        return { dx: dx * pageZoom, dy: dy * pageZoom };
173    }
174
175    function init() {
176        bind();
177    }
178
179    global.CitrusBrowserZoom = {
180        init: init,
181        refresh: apply,
182        recalibrate: function () {
183            recalibrateBaseline();
184            apply();
185        },
186        getScale: function () { return pageZoom; },
187        getCompensate: function () { return isCompensating(pageZoom) ? 1 / pageZoom : 1; },
188        isCompensating: function () { return isCompensating(pageZoom); },
189        toLogicalDelta: toLogicalDelta
190    };
191
192    if (document.readyState === 'loading') {
193        document.addEventListener('DOMContentLoaded', bind);
194    } else {
195        bind();
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.