PageSourceSearch

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

js pupwife.com collected 2026-09-30 15:04:51 UTC 7,949 bytes, 238 lines download raw bytes

1/**
2 * CitrusOS theme chrome — palette tokens, tray sync, meta theme-color.
3 * Uses the active palette id (not computed CSS) so every settings change re-applies.
4 */
5(function (global) {
6    'use strict';
7
8    var probe = null;
9
10    function getProbe() {
11        if (!probe && document.body) {
12            probe = document.createElement('span');
13            probe.setAttribute('aria-hidden', 'true');
14            probe.style.cssText = 'position:absolute;left:-9999px;top:-9999px;opacity:0;pointer-events:none;';
15            document.body.appendChild(probe);
16        }
17        return probe;
18    }
19
20    function parseRgbString(value) {
21        if (!value) return null;
22        var match = value.match(/rgba?\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)/i);
23        if (!match) return null;
24        return {
25            r: Math.round(parseFloat(match[1], 10)),
26            g: Math.round(parseFloat(match[2], 10)),
27            b: Math.round(parseFloat(match[3], 10))
28        };
29    }
30
31    function hexToRgb(hex) {
32        if (!hex) return null;
33        var clean = String(hex).replace('#', '');
34        if (clean.length === 3) {
35            clean = clean[0] + clean[0] + clean[1] + clean[1] + clean[2] + clean[2];
36        }
37        if (clean.length !== 6) return null;
38        return {
39            r: parseInt(clean.slice(0, 2), 16),
40            g: parseInt(clean.slice(2, 4), 16),
41            b: parseInt(clean.slice(4, 6), 16)
42        };
43    }
44
45    function readPaletteId() {
46        return document.documentElement.getAttribute('data-site-palette') || 'default';
47    }
48
49    function readAccentRgbFromPalette(paletteId) {
50        var api = global.SiteThemePalettes;
51        if (api && typeof api.getPaletteOption === 'function') {
52            var option = api.getPaletteOption(paletteId);
53            if (option && option.stripes && option.stripes[0]) {
54                var fromHex = hexToRgb(option.stripes[0]);
55                if (fromHex) return fromHex;
56            }
57        }
58
59        var node = getProbe();
60        var root = document.documentElement;
61        if (!node || !root) return null;
62
63        var parent = node.parentNode;
64        root.appendChild(node);
65        node.style.color = 'var(--accent-primary)';
66        var rgb = parseRgbString(getComputedStyle(node).color);
67        node.style.color = '';
68        if (parent) parent.appendChild(node);
69        return rgb;
70    }
71
72    function rgbToHsl(r, g, b) {
73        r /= 255;
74        g /= 255;
75        b /= 255;
76        var max = Math.max(r, g, b);
77        var min = Math.min(r, g, b);
78        var h = 0;
79        var s = 0;
80        var l = (max + min) / 2;
81
82        if (max !== min) {
83            var d = max - min;
84            s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
85            switch (max) {
86                case r: h = ((g - b) / d + (g < b ? 6 : 0)) / 6; break;
87                case g: h = ((b - r) / d + 2) / 6; break;
88                default: h = ((r - g) / d + 4) / 6; break;
89            }
90        }
91
92        return { h: h * 360, s: s, l: l };
93    }
94
95    function hslToRgb(h, s, l) {
96        h = ((h % 360) + 360) % 360;
97        var c = (1 - Math.abs(2 * l - 1)) * s;
98        var x = c * (1 - Math.abs((h / 60) % 2 - 1));
99        var m = l - c / 2;
100        var r = 0;
101        var g = 0;
102        var b = 0;
103
104        if (h < 60) { r = c; g = x; }
105        else if (h < 120) { r = x; g = c; }
106        else if (h < 180) { g = c; b = x; }
107        else if (h < 240) { g = x; b = c; }
108        else if (h < 300) { r = x; b = c; }
109        else { r = c; b = x; }
110
111        return {
112            r: Math.round((r + m) * 255),
113            g: Math.round((g + m) * 255),
114            b: Math.round((b + m) * 255)
115        };
116    }
117
118    function rgbToCss(rgb) {
119        return 'rgb(' + rgb.r + ', ' + rgb.g + ', ' + rgb.b + ')';
120    }
121
122    function isWarmAccent(hsl) {
123        return hsl.h < 58 || hsl.h >= 328;
124    }
125
126    function isCoolAccent(hsl) {
127        return hsl.h >= 135 && hsl.h <= 250;
128    }
129
130    function pickStartPalette(accentHsl, isDark) {
131        var warm = isWarmAccent(accentHsl);
132        var cool = isCoolAccent(accentHsl);
133        var saturated = accentHsl.s > 0.28;
134
135        if (warm && saturated) {
136            return {
137                hue: 198,
138                sat: isDark ? 0.52 : 0.48,
139                lightTop: isDark ? 0.46 : 0.42,
140                lightBottom: isDark ? 0.34 : 0.30,
141                text: '#ffffff'
142            };
143        }
144
145        if (cool && saturated) {
146            return {
147                hue: 36,
148                sat: isDark ? 0.78 : 0.82,
149                lightTop: isDark ? 0.56 : 0.60,
150                lightBottom: isDark ? 0.44 : 0.48,
151                text: '#1a1208'
152            };
153        }
154
155        return {
156            hue: 36,
157            sat: isDark ? 0.72 : 0.76,
158            lightTop: isDark ? 0.52 : 0.56,
159            lightBottom: isDark ? 0.40 : 0.44,
160            text: '#1a1208'
161        };
162    }
163
164    function buildStartGradients(paletteId, isDark) {
165        var accentRgb = readAccentRgbFromPalette(paletteId);
166        if (!accentRgb) return null;
167
168        var accentHsl = rgbToHsl(accentRgb.r, accentRgb.g, accentRgb.b);
169        var palette = pickStartPalette(accentHsl, isDark);
170        var top = hslToRgb(palette.hue, palette.sat, palette.lightTop);
171        var bottom = hslToRgb(palette.hue, palette.sat * 0.95, palette.lightBottom);
172        var topActive = hslToRgb(palette.hue, palette.sat * 0.95, palette.lightBottom);
173        var bottomActive = hslToRgb(palette.hue, palette.sat, palette.lightTop);
174
175        return {
176            bg: 'linear-gradient(180deg, ' + rgbToCss(top) + ' 0%, ' + rgbToCss(bottom) + ' 100%)',
177            bgActive: 'linear-gradient(180deg, ' + rgbToCss(topActive) + ' 0%, ' + rgbToCss(bottomActive) + ' 100%)',
178            text: palette.text
179        };
180    }
181
182    function syncTrayThemeIcon(isDark) {
183        var themeBtn = document.getElementById('citrusos-theme-btn');
184        if (!themeBtn) return;
185        var icon = themeBtn.querySelector('i');
186        if (icon) {
187            icon.className = isDark ? 'fa-solid fa-moon' : 'fa-solid fa-sun';
188        }
189    }
190
191    function syncMetaThemeColor() {
192        var meta = document.querySelector('meta[name="theme-color"]');
193        if (!meta || !document.body) return;
194        try {
195            var accent = getComputedStyle(document.body).getPropertyValue('--citrusos-accent-deep').trim();
196            if (accent) meta.setAttribute('content', accent);
197        } catch (e) { /* ignore */ }
198    }
199
200    function applyStartButtonChrome(paletteId, isDark) {
201        if (!document.body) return;
202        var gradients = buildStartGradients(paletteId, isDark);
203        if (!gradients) return;
204
205        document.body.style.setProperty('--citrusos-start-bg', gradients.bg);
206        document.body.style.setProperty('--citrusos-start-bg-active', gradients.bgActive);
207        document.body.style.setProperty('--citrusos-start-text', gradients.text);
208
209        var startBtn = document.getElementById('citrusos-start-btn');
210        if (startBtn) {
211            startBtn.style.removeProperty('background');
212            startBtn.style.removeProperty('color');
213        }
214    }
215
216    function apply(themeHint) {
217        if (!document.body || !document.body.classList.contains('page-citrusos')) return;
218
219        var paletteId = (themeHint && themeHint.palette) || readPaletteId();
220        var isDark = typeof (themeHint && themeHint.darkMode) === 'boolean'
221            ? themeHint.darkMode
222            : document.documentElement.classList.contains('dark-mode');
223
224        syncTrayThemeIcon(isDark);
225        syncMetaThemeColor();
226        applyStartButtonChrome(paletteId, isDark);
227    }
228
229    global.CitrusThemeChrome = {
230        apply: apply
231    };
232
233    if (document.readyState === 'loading') {
234        document.addEventListener('DOMContentLoaded', function () { apply(); });
235    } else {
236        apply();
237    }
238})(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.