PageSourceSearch

https://pupwife.com/scripts/effects/settingsMenu.js

js pupwife.com collected 2026-09-30 15:05:44 UTC 216,416 bytes, 4,160 lines download raw bytes

1// ====================================
2// PERSONALIZED SETTINGS MENU
3// Bottom-left floating settings panel
4// ====================================
5
6(function() {
7    'use strict';
8
9    function emitDevLog() {
10        if (typeof window !== 'undefined' && typeof window.devLog === 'function') {
11            window.devLog.apply(window, arguments);
12        }
13    }
14    function emitDevWarn() {
15        if (typeof window !== 'undefined' && typeof window.devWarn === 'function') {
16            window.devWarn.apply(window, arguments);
17        }
18    }
19
20    function isCitrusOsPage() {
21        return !!(document.body && document.body.classList.contains('page-citrusos'));
22    }
23
24    function normalizeCitrusUiScale(value) {
25        var parsed = parseInt(value, 10);
26        if (CITRUS_UI_SCALE_OPTIONS.indexOf(parsed) === -1) {
27            return DEFAULT_SETTINGS.citrusUiScale;
28        }
29        return parsed;
30    }
31
32    function applyCitrusUiScale(scale) {
33        var normalized = normalizeCitrusUiScale(scale != null ? scale : currentSettings.citrusUiScale);
34        var factor = normalized / 100;
35        document.documentElement.style.setProperty('--citrusos-ui-scale', String(factor));
36        if (isCitrusOsPage()) {
37            document.body.style.setProperty('--citrusos-ui-scale', String(factor));
38        }
39    }
40
41    function parseDarkMode(value, fallback) {
42        if (window.SiteThemeBoot && typeof window.SiteThemeBoot.parseDarkMode === 'function') {
43            return window.SiteThemeBoot.parseDarkMode(value, fallback);
44        }
45        if (typeof value === 'boolean') return value;
46        if (value === 'true' || value === 1) return true;
47        if (value === 'false' || value === 0) return false;
48        return fallback;
49    }
50
51    function readSiteDarkMode() {
52        if (window.SiteThemeToggle && typeof window.SiteThemeToggle.readDarkModeSetting === 'function') {
53            return window.SiteThemeToggle.readDarkModeSetting();
54        }
55        return parseDarkMode(currentSettings.darkMode, DEFAULT_SETTINGS.darkMode);
56    }
57
58    // Default settings
59    const DEFAULT_SETTINGS = {
60        darkMode: true, // Light/Dark theme toggle (default dark for new visitors)
61        palette: 'default', // Sitewide color palette (synced with chatroom)
62        follower: 'none', // 'none', 'marsey', 'skeletile', 'orca', 'dragon', or 'fish2D'
63        performanceMode: false,
64        clickSparks: true,
65        cursorTrail: 'none', // 'none', 'sparkles', 'fairy', 'rainbow', 'bubbles', 'hearts', 'stars', 'fire', 'snow', 'leaves'
66        trailAmount: 2, // 0=Minimum … 4=Extreme (Normal default)
67        customCursor: 'none', // 'none', 'asriel', 'axolotl', Rough.js, or Two.js cursors
68        seasonalOverlay: false, // Seasonal effects (Halloween, Christmas, Valentine's, etc.)
69        background: 'caustics', // Ambient stack preset — see siteBackgrounds.js
70        backgroundTuning: null, // Per-background shader tuning — merged on load
71        weatherOverlay: false, // Weather overlay toggle
72        homeLanding: true, // Homepage fluid landing animation (index full load)
73        uiSounds: false, // Micro UI sounds (off by default)
74        citrusUiScale: 100, // CitrusOS desktop UI scale: 100 | 125 | 150
75        musicPreload: 'off', // CitrusOS boot: off | metadata | full
76        desktopIconsVisible: true,
77        desktopIconLabelsVisible: true,
78        desktopIconScale: 100, // 75 | 100 | 125 | 150
79        citrusWallpaperDim: 0, // 0–50 desktop dim overlay
80        citrusWallpaperSaturation: 100, // 50–150
81        citrusWallpaperBrightness: 100, // 50–150
82        citrusWallpaperBlur: 0, // 0–12 px on desktop wallpaper layers
83        taskbarAutoHide: false,
84        taskbarClock24Hour: false,
85        taskbarClockShowSeconds: false,
86        taskbarShowDate: true,
87        showDesktopButton: true,
88        windowTitlebarDblClickMaximize: true,
89        citrusSettingsTab: 'appearance', // Last CitrusOS settings sidebar tab
90        trailColors: null, // { primary, secondary, tertiary } — merged on load
91        trailFavorites: [] // Favorite cursor trail ids
92    };
93
94    const CITRUS_UI_SCALE_OPTIONS = [100, 125, 150];
95    const DESKTOP_ICON_SCALE_OPTIONS = [75, 100, 125, 150];
96    const MUSIC_PRELOAD_OPTIONS = ['off', 'metadata', 'full'];
97
98    function normalizeMusicPreload(value) {
99        if (MUSIC_PRELOAD_OPTIONS.indexOf(value) !== -1) return value;
100        return DEFAULT_SETTINGS.musicPreload;
101    }
102
103    function normalizeDesktopIconScale(value) {
104        var parsed = parseInt(value, 10);
105        if (DESKTOP_ICON_SCALE_OPTIONS.indexOf(parsed) === -1) {
106            return DEFAULT_SETTINGS.desktopIconScale;
107        }
108        return parsed;
109    }
110
111    function clampSettingNumber(value, min, max, fallback) {
112        var n = parseInt(value, 10);
113        if (Number.isNaN(n)) return fallback;
114        return Math.max(min, Math.min(max, n));
115    }
116
117    const DEFAULT_WOIM_STRING = {
118        ropePoints: 32,
119        bodyPoints: 32,
120        size: 100,
121        ropeLength: 100,
122        ropeThickness: 100,
123        anchorShow: true,
124        anchorSize: 100,
125        anchorThickness: 100,
126        woim: { colorMode: 'solid', solidColor: '#ff4da6', rgbSpeed: 1, pridePreset: 'rainbow' },
127        rope: { colorMode: 'solid', solidColor: '#b8b8c8', rgbSpeed: 1, pridePreset: 'rainbow' }
128    };
129
130    const DEFAULT_JELLYPUFF = {
131        size: 100,
132        pulseRate: 100,
133        tentacleDensity: 100,
134        glowStrength: 100,
135        bell: { colorMode: 'solid', solidColor: '#0a1828', rgbSpeed: 1, pridePreset: 'rainbow' },
136        tentacles: { colorMode: 'solid', solidColor: '#2a5878', rgbSpeed: 1, pridePreset: 'rainbow' }
137    };
138
139    let woimColorPanels = { rope: null, woim: null };
140    let jellyColorPanels = { bell: null, tentacles: null };
141
142    const DEFAULT_CAT_PAW_ARM = {
143        anchorPreset: 'bottom-center',
144        originX: 50,
145        originY: 108,
146        armThickness: 100,
147        armTaper: 35,
148        armBend: 0,
149        armMargin: 100,
150        followLag: 42,
151        clickSquish: true,
152        showBeans: true,
153        pawPreset: 'classic-white',
154        pawSpecies: 'cat',
155        pawSize: 100,
156        padSize: 100,
157        printsEnabled: true,
158        printSize: 100,
159        printFade: 100,
160        printMax: 16,
161        outlineShow: true,
162        outlineThickness: 100,
163        arm: { colorMode: 'solid', solidColor: '#ffffff', rgbSpeed: 1, pridePreset: 'rainbow' },
164        pawFur: { colorMode: 'solid', solidColor: '#ffffff', rgbSpeed: 1, pridePreset: 'rainbow' },
165        pawPads: { colorMode: 'solid', solidColor: '#f2a7b8', rgbSpeed: 1, pridePreset: 'rainbow' },
166        outline: { colorMode: 'solid', solidColor: '#8b6f5c', rgbSpeed: 1, pridePreset: 'rainbow' },
167        prints: { colorMode: 'solid', solidColor: '#e89ab0', rgbSpeed: 1, pridePreset: 'rainbow' }
168    };
169
170    let catPawColorPanels = { arm: null, pawFur: null, pawPads: null, outline: null, prints: null, spriteFur: null };
171    let catPawSpeciesSelect = null;
172    let catPawPresetSelect = null;
173    let catPawAnchorSelect = null;
174
175    const CAT_PAW_ANCHOR_OPTIONS = [
176        { value: 'bottom-center', label: 'bottom center' },
177        { value: 'bottom-left', label: 'bottom left' },
178        { value: 'bottom-right', label: 'bottom right' },
179        { value: 'top-center', label: 'top center' },
180        { value: 'top-left', label: 'top left' },
181        { value: 'top-right', label: 'top right' },
182        { value: 'middle-left', label: 'middle left' },
183        { value: 'middle-right', label: 'middle right' },
184        { value: 'custom', label: 'custom' }
185    ];
186
187    const CAT_PAW_SPECIES_OPTIONS = [
188        { value: 'cat', label: 'cat paw' },
189        { value: 'dog', label: 'dog paw' }
190    ];
191
192    function mergeCatPawArm(raw) {
193        if (window.CatPawArmCursor && typeof window.CatPawArmCursor.mergeConfig === 'function') {
194            return window.CatPawArmCursor.mergeConfig(raw);
195        }
196        var d = DEFAULT_CAT_PAW_ARM;
197        if (!raw || typeof raw !== 'object') {
198            return {
199                anchorPreset: d.anchorPreset,
200                originX: d.originX,
201                originY: d.originY,
202                armThickness: d.armThickness,
203                armTaper: d.armTaper,
204                armBend: d.armBend,
205                armMargin: d.armMargin,
206                followLag: d.followLag,
207                clickSquish: d.clickSquish,
208                showBeans: d.showBeans,
209                pawPreset: d.pawPreset,
210                pawSpecies: d.pawSpecies,
211                pawSize: d.pawSize,
212                padSize: d.padSize,
213                printsEnabled: d.printsEnabled,
214                printSize: d.printSize,
215                printFade: d.printFade,
216                printMax: d.printMax,
217                outlineShow: d.outlineShow,
218                outlineThickness: d.outlineThickness,
219                arm: Object.assign({}, d.arm),
220                pawFur: Object.assign({}, d.pawFur),
221                pawPads: Object.assign({}, d.pawPads),
222                outline: Object.assign({}, d.outline),
223                prints: Object.assign({}, d.prints)
224            };
225        }
226        var pawSize = raw.pawSize !== undefined ? raw.pawSize : d.pawSize;
227        return {
228            anchorPreset: raw.anchorPreset || d.anchorPreset,
229            originX: raw.originX !== undefined ? raw.originX : d.originX,
230            originY: raw.originY !== undefined ? raw.originY : d.originY,
231            armThickness: pawSize,
232            armTaper: raw.armTaper !== undefined ? raw.armTaper : d.armTaper,
233            armBend: raw.armBend !== undefined ? raw.armBend : d.armBend,
234            armMargin: raw.armMargin !== undefined ? raw.armMargin : d.armMargin,
235            followLag: raw.followLag !== undefined ? raw.followLag : d.followLag,
236            clickSquish: typeof raw.clickSquish === 'boolean' ? raw.clickSquish : d.clickSquish,
237            showBeans: typeof raw.showBeans === 'boolean' ? raw.showBeans : d.showBeans,
238            pawPreset: raw.pawPreset || d.pawPreset,
239            pawSpecies: raw.pawSpecies === 'dog' ? 'dog' : 'cat',
240            pawSize: pawSize,
241            padSize: raw.padSize !== undefined ? raw.padSize : d.padSize,
242            printsEnabled: typeof raw.printsEnabled === 'boolean' ? raw.printsEnabled : d.printsEnabled,
243            printSize: raw.printSize !== undefined ? raw.printSize : d.printSize,
244            printFade: raw.printFade !== undefined ? raw.printFade : d.printFade,
245            printMax: raw.printMax !== undefined ? raw.printMax : d.printMax,
246            outlineShow: typeof raw.outlineShow === 'boolean' ? raw.outlineShow : d.outlineShow,
247            outlineThickness: raw.outlineThickness !== undefined ? raw.outlineThickness : d.outlineThickness,
248            arm: Object.assign({}, d.arm, raw.arm || {}),
249            pawFur: Object.assign({}, d.pawFur, raw.pawFur || {}),
250            pawPads: Object.assign({}, d.pawPads, raw.pawPads || {}),
251            outline: Object.assign({}, d.outline, raw.outline || {}),
252            prints: Object.assign({}, d.prints, raw.prints || {})
253        };
254    }
255
256    function setCatPawArm(patch, opts) {
257        opts = opts || {};
258        var prev = mergeCatPawArm(currentSettings.catPawArm);
259        var next = Object.assign({}, prev, patch || {});
260        ['arm', 'pawFur', 'pawPads', 'outline', 'prints'].forEach(function (key) {
261            if (patch && patch[key]) next[key] = Object.assign({}, prev[key], patch[key]);
262        });
263        var merged = mergeCatPawArm(next);
264        currentSettings.catPawArm = merged;
265        saveSettings();
266        if (window.CatPawArmCursor && typeof window.CatPawArmCursor.applySettings === 'function') {
267            window.CatPawArmCursor.applySettings(merged);
268        }
269        try {
270            document.dispatchEvent(new CustomEvent('pupwife:setting-changed', {
271                detail: { key: 'catPawArm', value: merged }
272            }));
273        } catch (e) { /* ignore */ }
274        if (opts.syncUi !== false) {
275            syncCatPawSettingsUI(document.getElementById('settings-panel'));
276        }
277    }
278
279    function getCatPawAnchorPresetMap() {
280        if (window.CatPawArmCursor && window.CatPawArmCursor.ANCHOR_PRESETS) {
281            return window.CatPawArmCursor.ANCHOR_PRESETS;
282        }
283        return {
284            'bottom-center': { originX: 50, originY: 108 },
285            'bottom-left': { originX: 8, originY: 108 },
286            'bottom-right': { originX: 92, originY: 108 },
287            'top-center': { originX: 50, originY: -8 },
288            'top-left': { originX: 8, originY: -8 },
289            'top-right': { originX: 92, originY: -8 },
290            'middle-left': { originX: 0, originY: 50 },
291            'middle-right': { originX: 100, originY: 50 }
292        };
293    }
294
295    function snapCatPawAnchorPreset(preset) {
296        var map = getCatPawAnchorPresetMap();
297        var coords = map[preset];
298        if (!coords) return null;
299        return { anchorPreset: preset, originX: coords.originX, originY: coords.originY };
300    }
301
302    function randomHexColor() {
303        return '#' + Math.floor(Math.random() * 0xffffff).toString(16).padStart(6, '0');
304    }
305
306    function randomColorPart() {
307        var modes = ['solid', 'rgb', 'pride'];
308        var mode = modes[Math.floor(Math.random() * modes.length)];
309        var pride = ['rainbow', 'trans', 'bi', 'pan', 'lesbian', 'nb', 'ace', 'progress'];
310        return {
311            colorMode: mode,
312            solidColor: randomHexColor(),
313            rgbSpeed: Math.round((0.2 + Math.random() * 2.8) * 10) / 10,
314            pridePreset: pride[Math.floor(Math.random() * pride.length)]
315        };
316    }
317
318    function getCatPawPresetOptions() {
319        if (window.CatPawArmCursor && typeof window.CatPawArmCursor.getPresetOptions === 'function') {
320            return window.CatPawArmCursor.getPresetOptions();
321        }
322        return [{ value: 'classic-white', label: 'Classic white' }];
323    }
324
325    function catPawUsesSpriteArt(presetId) {
326        var p = presetId || mergeCatPawArm(currentSettings.catPawArm).pawPreset;
327        if (!window.CatPawArmCursor) return false;
328        if (typeof window.CatPawArmCursor.hasPresetArt === 'function') {
329            return window.CatPawArmCursor.hasPresetArt(p);
330        }
331        if (typeof window.CatPawArmCursor.hasSpriteArt === 'function') {
332            return window.CatPawArmCursor.hasSpriteArt(p);
333        }
334        return false;
335    }
336
337    function updateCatPawArtModeUI(panel) {
338        if (!panel) return;
339        var spriteArt = catPawUsesSpriteArt();
340        var pawPanel = panel.querySelector('#cat-paw-arm-settings');
341        if (pawPanel) pawPanel.classList.toggle('catpaw-art-mode', spriteArt);
342        panel.querySelectorAll('.catpaw-procedural-only').forEach(function (el) {
343            el.hidden = spriteArt;
344        });
345        panel.querySelectorAll('.catpaw-sprite-only').forEach(function (el) {
346            el.hidden = !spriteArt;
347        });
348        var speciesField = panel.querySelector('.catpaw-species-field');
349        if (speciesField) speciesField.hidden = spriteArt;
350        var note = panel.querySelector('.catpaw-sprite-art-note');
351        if (note) {
352            note.hidden = !spriteArt;
353            note.textContent = 'Fur and arm tint together. Turn off toe beans for the back of the paw.';
354        }
355    }
356
357    function randomizeCatPawColors(opts) {
358        opts = opts || {};
359        if (catPawUsesSpriteArt()) return;
360        setCatPawArm({
361            arm: randomColorPart(),
362            pawFur: randomColorPart(),
363            pawPads: randomColorPart(),
364            outline: randomColorPart(),
365            prints: randomColorPart()
366        });
367        if (opts.syncUI !== false) {
368            syncCatPawSettingsUI(document.getElementById('settings-panel'));
369        }
370    }
371
372    function randomizeCatPawShape(opts) {
373        opts = opts || {};
374        var presets = CAT_PAW_ANCHOR_OPTIONS.filter(function (o) { return o.value !== 'custom'; });
375        var pick = presets[Math.floor(Math.random() * presets.length)];
376        var snap = snapCatPawAnchorPreset(pick.value) || {};
377        setCatPawArm(Object.assign({
378            pawSpecies: Math.random() < 0.5 ? 'cat' : 'dog',
379            armTaper: Math.floor(Math.random() * 101),
380            armBend: Math.floor(Math.random() * 201),
381            followLag: 5 + Math.floor(Math.random() * 96),
382            clickSquish: Math.random() < 0.9,
383            showBeans: Math.random() < 0.85,
384            pawSize: 25 + Math.floor(Math.random() * 376),
385            padSize: 50 + Math.floor(Math.random() * 151),
386            printsEnabled: Math.random() < 0.9,
387            printSize: 25 + Math.floor(Math.random() * 176),
388            printFade: 25 + Math.floor(Math.random() * 176),
389            printMax: 4 + Math.floor(Math.random() * 29),
390            outlineShow: Math.random() < 0.95,
391            outlineThickness: 50 + Math.floor(Math.random() * 151)
392        }, snap));
393        if (opts.syncUI !== false) {
394            syncCatPawSettingsUI(document.getElementById('settings-panel'));
395        }
396    }
397
398    function randomizeCatPawAll(opts) {
399        randomizeCatPawColors({ syncUI: false });
400        randomizeCatPawShape({ syncUI: false });
401        syncCatPawSettingsUI(document.getElementById('settings-panel'));
402    }
403
404    function mergeWoimString(raw) {
405        if (window.WoimStringFollower && typeof window.WoimStringFollower.mergeConfig === 'function') {
406            return window.WoimStringFollower.mergeConfig(raw);
407        }
408        var d = DEFAULT_WOIM_STRING;
409        if (!raw || typeof raw !== 'object') {
410            return {
411                ropePoints: d.ropePoints,
412                bodyPoints: d.bodyPoints,
413                size: d.size,
414                ropeLength: d.ropeLength,
415                ropeThickness: d.ropeThickness,
416                anchorShow: d.anchorShow,
417                anchorSize: d.anchorSize,
418                anchorThickness: d.anchorThickness,
419                woim: Object.assign({}, d.woim),
420                rope: Object.assign({}, d.rope)
421            };
422        }
423        return {
424            ropePoints: parseWoimNotch(raw.ropePoints, 16, 64, d.ropePoints),
425            bodyPoints: parseWoimNotch(raw.bodyPoints, 16, 64, d.bodyPoints),
426            size: raw.size !== undefined ? raw.size : d.size,
427            ropeLength: raw.ropeLength !== undefined ? raw.ropeLength : d.ropeLength,
428            ropeThickness: raw.ropeThickness !== undefined ? raw.ropeThickness : d.ropeThickness,
429            anchorShow: typeof raw.anchorShow === 'boolean' ? raw.anchorShow : d.anchorShow,
430            anchorSize: raw.anchorSize !== undefined ? raw.anchorSize : d.anchorSize,
431            anchorThickness: raw.anchorThickness !== undefined ? raw.anchorThickness : d.anchorThickness,
432            woim: Object.assign({}, d.woim, raw.woim || {}),
433            rope: Object.assign({}, d.rope, raw.rope || {})
434        };
435    }
436
437    function setWoimString(patch) {
438        var prev = mergeWoimString(currentSettings.woimString);
439        var next = Object.assign({}, prev, patch || {});
440        if (patch && patch.woim) next.woim = Object.assign({}, prev.woim, patch.woim);
441        if (patch && patch.rope) next.rope = Object.assign({}, prev.rope, patch.rope);
442        setSetting('woimString', mergeWoimString(next));
443    }
444
445    function mergeJellypuff(raw) {
446        if (window.JellypuffFollower && typeof window.JellypuffFollower.mergeConfig === 'function') {
447            return window.JellypuffFollower.mergeConfig(raw);
448        }
449        var d = DEFAULT_JELLYPUFF;
450        if (!raw || typeof raw !== 'object') {
451            return {
452                size: d.size,
453                pulseRate: d.pulseRate,
454                tentacleDensity: d.tentacleDensity,
455                glowStrength: d.glowStrength,
456                bell: Object.assign({}, d.bell),
457                tentacles: Object.assign({}, d.tentacles)
458            };
459        }
460        return {
461            size: raw.size !== undefined ? raw.size : d.size,
462            pulseRate: raw.pulseRate !== undefined ? raw.pulseRate : d.pulseRate,
463            tentacleDensity: raw.tentacleDensity !== undefined ? raw.tentacleDensity : d.tentacleDensity,
464            glowStrength: raw.glowStrength !== undefined ? raw.glowStrength : d.glowStrength,
465            bell: Object.assign({}, d.bell, raw.bell || {}),
466            tentacles: Object.assign({}, d.tentacles, raw.tentacles || {})
467        };
468    }
469
470    function setJellypuff(patch) {
471        var prev = mergeJellypuff(currentSettings.jellypuff);
472        var next = Object.assign({}, prev, patch || {});
473        if (patch && patch.bell) next.bell = Object.assign({}, prev.bell, patch.bell);
474        if (patch && patch.tentacles) next.tentacles = Object.assign({}, prev.tentacles, patch.tentacles);
475        setSetting('jellypuff', mergeJellypuff(next));
476    }
477
478    function parseWoimNotch(value, min, max, fallback) {
479        var n = parseInt(value, 10);
480        if (Number.isNaN(n)) return fallback;
481        return Math.max(min, Math.min(max, n));
482    }
483
484    // Load settings from localStorage or use defaults
485    let currentSettings = loadSettings();
486
487    // ====================================
488    // SETTINGS STORAGE
489    // ====================================
490
491    function loadSettings() {
492        try {
493            const saved = localStorage.getItem('puppywifey_settings');
494            if (saved) {
495                const parsed = JSON.parse(saved);
496                // Merge with defaults in case new settings were added
497                const merged = { ...DEFAULT_SETTINGS, ...parsed };
498                merged.darkMode = parseDarkMode(merged.darkMode, DEFAULT_SETTINGS.darkMode);
499                if (window.SiteThemePalettes && typeof window.SiteThemePalettes.readMergedThemeSettings === 'function') {
500                    const theme = window.SiteThemePalettes.readMergedThemeSettings();
501                    merged.palette = theme.palette;
502                    merged.darkMode = theme.darkMode;
503                } else if (!merged.palette) {
504                    merged.palette = DEFAULT_SETTINGS.palette;
505                }
506                if (typeof merged.uiSounds !== 'boolean') {
507                    merged.uiSounds = DEFAULT_SETTINGS.uiSounds;
508                }
509                merged.citrusUiScale = normalizeCitrusUiScale(merged.citrusUiScale);
510                merged.musicPreload = normalizeMusicPreload(merged.musicPreload);
511                merged.desktopIconsVisible = typeof merged.desktopIconsVisible === 'boolean'
512                    ? merged.desktopIconsVisible
513                    : DEFAULT_SETTINGS.desktopIconsVisible;
514                merged.desktopIconLabelsVisible = typeof merged.desktopIconLabelsVisible === 'boolean'
515                    ? merged.desktopIconLabelsVisible
516                    : DEFAULT_SETTINGS.desktopIconLabelsVisible;
517                merged.desktopIconScale = normalizeDesktopIconScale(merged.desktopIconScale);
518                merged.citrusWallpaperDim = clampSettingNumber(
519                    merged.citrusWallpaperDim, 0, 50, DEFAULT_SETTINGS.citrusWallpaperDim
520                );
521                merged.citrusWallpaperSaturation = clampSettingNumber(
522                    merged.citrusWallpaperSaturation, 50, 150, DEFAULT_SETTINGS.citrusWallpaperSaturation
523                );
524                merged.citrusWallpaperBrightness = clampSettingNumber(
525                    merged.citrusWallpaperBrightness, 50, 150, DEFAULT_SETTINGS.citrusWallpaperBrightness
526                );
527                merged.citrusWallpaperBlur = clampSettingNumber(
528                    merged.citrusWallpaperBlur, 0, 12, DEFAULT_SETTINGS.citrusWallpaperBlur
529                );
530                merged.taskbarAutoHide = typeof merged.taskbarAutoHide === 'boolean'
531                    ? merged.taskbarAutoHide
532                    : DEFAULT_SETTINGS.taskbarAutoHide;
533                merged.taskbarClock24Hour = typeof merged.taskbarClock24Hour === 'boolean'
534                    ? merged.taskbarClock24Hour
535                    : DEFAULT_SETTINGS.taskbarClock24Hour;
536                merged.taskbarClockShowSeconds = typeof merged.taskbarClockShowSeconds === 'boolean'
537                    ? merged.taskbarClockShowSeconds
538                    : DEFAULT_SETTINGS.taskbarClockShowSeconds;
539                merged.taskbarShowDate = typeof merged.taskbarShowDate === 'boolean'
540                    ? merged.taskbarShowDate
541                    : DEFAULT_SETTINGS.taskbarShowDate;
542                merged.showDesktopButton = typeof merged.showDesktopButton === 'boolean'
543                    ? merged.showDesktopButton
544                    : DEFAULT_SETTINGS.showDesktopButton;
545                merged.windowTitlebarDblClickMaximize = typeof merged.windowTitlebarDblClickMaximize === 'boolean'
546                    ? merged.windowTitlebarDblClickMaximize
547                    : DEFAULT_SETTINGS.windowTitlebarDblClickMaximize;
548                var validSettingsTabs = ['account', 'appearance', 'desktop', 'cursor', 'effects', 'advanced'];
549                if (validSettingsTabs.indexOf(merged.citrusSettingsTab) === -1) {
550                    merged.citrusSettingsTab = DEFAULT_SETTINGS.citrusSettingsTab;
551                }
552                if (merged.trailAmount === undefined || merged.trailAmount === null) {
553                    merged.trailAmount = DEFAULT_SETTINGS.trailAmount;
554                } else {
555                    const parsedAmount = parseInt(merged.trailAmount, 10);
556                    merged.trailAmount = Number.isNaN(parsedAmount)
557                        ? DEFAULT_SETTINGS.trailAmount
558                        : Math.max(0, Math.min(4, parsedAmount));
559                }
560                if (merged.cursorTrail === 'worms') merged.cursorTrail = 'woims';
561                if (merged.cursorTrail === 'gummyWorms') merged.cursorTrail = 'gummyWoims';
562                if (merged.follower === 'koi') merged.follower = 'fish2D';
563                if (!merged.background || typeof merged.background !== 'string') {
564                    merged.background = DEFAULT_SETTINGS.background;
565                }
565 else if (window.SiteBackgrounds && typeof window.SiteBackgrounds.normalizeId === 'function') {
566                    merged.background = window.SiteBackgrounds.normalizeId(merged.background);
567                } else if (merged.background !== 'caustics' && merged.background !== 'horizon' && merged.background !== 'clouds' && merged.background !== 'aquarium') {
568                    merged.background = DEFAULT_SETTINGS.background;
569                }
570                if (window.SiteBackgroundTuning && typeof window.SiteBackgroundTuning.mergeSettings === 'function') {
571                    merged.backgroundTuning = window.SiteBackgroundTuning.mergeSettings(parsed.backgroundTuning);
572                }
573                // Retired Matter.js custom cursors
574                if (merged.customCursor && String(merged.customCursor).startsWith('matter-')) {
575                    merged.customCursor = 'none';
576                }
577                // Retired toggles — always off (UI removed)
578                delete merged.fishGlow;
579                delete merged.shadows;
580                delete merged.navConfetti;
581                // Retired toggles — always on (UI removed)
582                delete merged.caustics;
583                delete merged.aurora;
584                delete merged.bubbles;
585                merged.woimString = mergeWoimString(parsed.woimString);
586                merged.jellypuff = mergeJellypuff(parsed.jellypuff);
587                merged.catPawArm = mergeCatPawArm(parsed.catPawArm);
588                Object.keys(merged).forEach(function (key) {
589                    if (key.indexOf('catpaw-') === 0) delete merged[key];
590                });
591                if (window.PupwifeTrailColorSupport && typeof window.PupwifeTrailColorSupport.mergeTrailColors === 'function') {
592                    merged.trailColors = window.PupwifeTrailColorSupport.mergeTrailColors(parsed.trailColors);
593                } else if (parsed.trailColors && typeof parsed.trailColors === 'object') {
594                    merged.trailColors = parsed.trailColors;
595                } else {
596                    merged.trailColors = { primary: '#c8a8ff', secondary: '#ffb3ba', tertiary: '#89cff0' };
597                }
598                merged.trailFavorites = Array.isArray(parsed.trailFavorites)
599                    ? parsed.trailFavorites.filter(function (v) { return typeof v === 'string' && v; })
600                    : DEFAULT_SETTINGS.trailFavorites;
601                return merged;
602            }
603        } catch (e) {
604            emitDevWarn('Failed to load settings:', e);
605        }
606        return Object.assign({ ...DEFAULT_SETTINGS }, {
607            woimString: mergeWoimString({}),
608            jellypuff: mergeJellypuff({}),
609            catPawArm: mergeCatPawArm({}),
610            trailColors: window.PupwifeTrailColorSupport
611                ? window.PupwifeTrailColorSupport.mergeTrailColors(null)
612                : { primary: '#c8a8ff', secondary: '#ffb3ba', tertiary: '#89cff0' },
613            trailFavorites: []
614        });
615    }
616
617    function saveSettings() {
618        try {
619            localStorage.setItem('puppywifey_settings', JSON.stringify(currentSettings));
620            emitDevLog('Settings saved!', currentSettings);
621        } catch (e) {
622            emitDevWarn('Failed to save settings:', e);
623        }
624    }
625
626    function getSetting(key) {
627        return currentSettings[key];
628    }
629
630    function setSetting(key, value) {
631        currentSettings[key] = value;
632        saveSettings();
633        applySettings();
634        try {
635            document.dispatchEvent(new CustomEvent('pupwife:setting-changed', {
636                detail: { key: key, value: value }
637            }));
638        } catch (e) { /* ignore */ }
639    }
640
641    function setSettingQuiet(key, value) {
642        currentSettings[key] = value;
643        saveSettings();
644    }
645
646    // ====================================
647    // APPLY SETTINGS TO PAGE
648    // ====================================
649
650    function applySettings() {
651        emitDevLog('Settings applying...', currentSettings);
652
653        // Theme — palette everywhere; light/dark via chatroom when on chatroom route
654        let themeState = {
655            palette: currentSettings.palette || DEFAULT_SETTINGS.palette,
656            colorMode: currentSettings.darkMode ? 'dark' : 'light',
657            darkMode: !!currentSettings.darkMode
658        };
659        if (window.SiteThemeToggle && typeof window.SiteThemeToggle.readThemeSettings === 'function') {
660            const synced = window.SiteThemeToggle.readThemeSettings();
661            themeState.palette = currentSettings.palette || synced.palette;
662            if (isChatroomPage()) {
663                themeState.colorMode = synced.colorMode;
664                themeState.darkMode = synced.darkMode;
665            }
666        }
667        currentSettings.palette = themeState.palette;
668        if (window.SiteThemeToggle && typeof window.SiteThemeToggle.a
668pplyThemeState === 'function') {
669            window.SiteThemeToggle.applyThemeState(themeState);
670        } else if (!/chatroom/.test(window.location.pathname)) {
671            const isDark = themeState.darkMode;
672            document.body.classList.toggle('dark-mode', isDark);
673            document.documentElement.classList.toggle('dark-mode', isDark);
674            document.documentElement.setAttribute('data-theme', isDark ? 'dark' : 'light');
675            document.documentElement.setAttribute('data-site-palette', themeState.palette);
676            document.documentElement.setAttribute('data-color-mode', themeState.colorMode);
677        }
678        updateDarkModeUI(isChatroomPage() ? isChatroomDarkTheme() : themeState.darkMode);
679        setPalettePickerValue(themeState.palette);
680
681        // Ambient background stack (caustics preset by default; extensible via siteBackgrounds.js)
682        if (window.SiteBackgroundApply && typeof window.SiteBackgroundApply.applyFromSettings === 'function') {
683            window.SiteBackgroundApply.applyFromSettings();
684        }
685
686        if (window.SiteBackgroundTuning && typeof window.SiteBackgroundTuning.applyAll === 'function') {
687            window.SiteBackgroundTuning.applyAll(currentSettings.backgroundTuning);
688        } else {
689            toggleEffect('caustics', true);
690            toggleEffect('aurora', true);
691            toggleEffect('bubbles', true);
692        }
693
694        if (currentSettings.performanceMode) {
695            toggleEffect('clickSparks', false);
696            toggleEffect('seasonalOverlay', false);
697            toggleEffect('weatherOverlay', false);
698            toggleRetiredEffects(false);
699            
700            // Disable cursor trail in performance mode
701            toggleCursorTrail('none');
702
703            if (window.CatPawArmCursor && typeof window.CatPawArmCursor.setCursor === 'function') {
704                window.CatPawArmCursor.setCursor('none');
705            }
706            
707            // Set global performance flag (bubbles ignore this — see bubbles.js)
708            window.PERFORMANCE_MODE_ENABLED = true;
709            
710            // Notify perfModeToggle.js if it exists
711            if (typeof window.togglePerformanceMode === 'function') {
712                const currentPerfMode = window.PERFORMANCE_MODE_ENABLED || false;
713                if (!currentPerfMode) {
714                    window.togglePerformanceMode();
715                }
716            }
717        } else {
718            window.PERFORMANCE_MODE_ENABLED = false;
719            
720            toggleRetiredEffects(false);
721            toggleEffect('clickSparks', currentSettings.clickSparks);
722            toggleEffect('seasonalOverlay', currentSettings.seasonalOverlay);
723            toggleEffect('weatherOverlay', currentSettings.weatherOverlay);
724        }
725
726        // Follower selection
727        if (window.switchFollower && typeof window.switchFollower === 'function') {
728            // Check if we're on chat page
729            const isOnChatPage = window.location.pathname.includes('chat.html') || 
730                                window.location.pathname.endsWith('chat');
731            
732            // Force "none" on chat page, otherwise apply saved setting
733            if (isOnChatPage) {
734                emitDevLog('⚠️ Chat page detected - forcing follower to "none"');
735                window.switchFollower('none');
736            } else {
737                const savedFollower = getSetting('follower');
738                if (savedFollower) {
739                    window.switchFollower(savedFollower, false);
740                }
741            }
742        }
743
744        // Cursor trail (independent toggle)
745        toggleCursorTrail(currentSettings.cursorTrail);
746        toggleCursorTrailAmount(currentSettings.trailAmount);
747        applyTrailColorsSetting(currentSettings.trailColors);
748        
749        // Custom cursor
750        toggleCustomCursor(currentSettings.customCursor);
751
752        // Homepage landing — dismiss immediately if turned off mid-landing
753        if (!currentSettings.homeLanding &&
754            window.HomeLanding &&
755            typeof window.HomeLanding.isActive === 'function' &&
756            window.HomeLanding.isActive()) {
757            window.HomeLanding.destroy();
758        }
759
760        applyCitrusUiScale(currentSettings.citrusUiScale);
761        applyCitrusDesktopPrefs();
762        applyCitrusTaskbarPrefs();
763        applySitewideWallpaperFilters();
764    }
765
766    function applyCitrusTaskbarPrefs() {
767        if (!isCitrusOsPage()) return;
768        if (window.CitrusDesktop && typeof window.CitrusDesktop.applyTaskbarPrefs === 'function') {
769            window.CitrusDesktop.applyTaskbarPrefs({
770                taskbarAutoHide: currentSettings.taskbarAutoHide,
771                taskbarClock24Hour: currentSettings.taskbarClock24Hour,
772                taskbarClockShowSeconds: currentSettings.taskbarClockShowSeconds,
773                taskbarShowDate: currentSettings.taskbarShowDate,
774                showDesktopButton: currentSettings.showDesktopButton,
775                windowTitlebarDblClickMaximize: currentSettings.windowTitlebarDblClickMaximize
776            });
777        }
778    }
779
780    function getWallpaperFilterLayerIds() {
781        var ids = ['page-background', 'aurora-overlay', 'webgl-overlay'];
782        if (document.documentElement.getAttribute('data-site-background') === 'aquarium') {
783            ids.push('site-aquarium-ocean', 'site-aquarium-plants', 'site-aquarium-fish', 'fish-glow-overlay');
784        }
785        return ids;
786    }
787
788    function applyWallpaperLayerFilters(sat, brightness, blur) {
789        var filterParts = [];
790        if (sat !== 100) filterParts.push('saturate(' + (sat / 100) + ')');
791        if (brightness !== 100) filterParts.push('brightness(' + (brightness / 100) + ')');
792        if (blur > 0) filterParts.push('blur(' + blur + 'px)');
793        var filter = filterParts.length ? filterParts.join(' ') : '';
794        getWallpaperFilterLayerIds().forEach(function (id) {
795            var el = document.getElementById(id);
796            if (el) el.style.filter = filter;
797        });
798    }
799
800    function applyCitrusDesktopPrefs() {
801        if (!isCitrusOsPage()) return;
802
803        if (window.CitrusDesktop && typeof window.CitrusDesktop.applyDesktopIconPrefs === 'function') {
804            window.CitrusDesktop.applyDesktopIconPrefs({
805                desktopIconsVisible: currentSettings.desktopIconsVisible,
806                desktopIconLabelsVisible: currentSettings.desktopIconLabelsVisible,
807                desktopIconScale: currentSettings.desktopIconScale
808            });
809        }
810
811        var dim = currentSettings.citrusWallpaperDim || 0;
812        var sat = currentSettings.citrusWallpaperSaturation != null
813            ? currentSettings.citrusWallpaperSaturation
814            : DEFAULT_SETTINGS.citrusWallpaperSaturation;
815        var brightness = currentSettings.citrusWallpaperBrightness != null
816            ? currentSettings.citrusWallpaperBrightness
817            : DEFAULT_SETTINGS.citrusWallpaperBrightness;
818        var blur = currentSettings.citrusWallpaperBlur != null
819            ? currentSettings.citrusWallpaperBlur
820            : DEFAULT_SETTINGS.citrusWallpaperBlur;
821        var tint = document.getElementById('citrusos-wallpaper-tint');
822        if (!tint) {
823            tint = document.createElement('div');
824            tint.id = 'citrusos-wallpaper-tint';
825            var shell = document.getElementById('citrusos-shell');
826            if (shell) {
827                shell.insertAdjacentElement('afterbegin', tint);
828            } else {
829                document.body.appendChild(tint);
830            }
831        }
832        tint.style.background = dim > 0 ? 'rgba(0, 0, 0, ' + (dim / 100) + ')' : 'transparent';
833
834        applyWallpaperLayerFilters(sat, brightness, blur);
835    }
836
837    function applySitewideWallpaperFilters() {
838        if (isCitrusOsPage()) return;
839        var sat = currentSettings.citrusWallpaperSaturation != null
840            ? currentSettings.citrusWallpaperSaturation
841            : DEFAULT_SETTINGS.citrusWallpaperSaturation;
842        var brightness = currentSettings.citrusWallpaperBrightness != null
843            ? currentSettings.citrusWallpaperBrightness
844            : DEFAULT_SETTINGS.citrusWallpaperBrightness;
845        var blur = currentSettings.citrusWallpaperBlur != null
846            ? currentSettings.citrusWallpaperBlur
847            : DEFAULT_SETTINGS.citrusWallpaperBlur;
848        applyWallpaperLayerFilters(sat, brightness, blur);
849    }
850
851    function applyWallpaperFilters() {
852        if (isCitrusOsPage()) {
853            applyCitrusDesktopPrefs();
854            return;
855        }
856        applySitewideWallpaperFilters();
857    }
858
859    function toggleRetiredEffects(enabled) {
860        toggleEffect('fishGlow', enabled);
861        toggleEffect('shadows', enabled);
862        toggleEffect('navConfetti', enabled);
863    }
864
865    function toggleEffect(effectName, enabled) {
866        const effectMap = {
867            fishGlow: 'fish-glow-overlay',
868            shadows: null, // handled by global flag
869            caustics: 'webgl-overlay',
870            aurora: 'aurora-overlay',
871            clickSparks: null, // handled by global flag
872            seasonalOverlay: null, // handled by seasonalOverlay instance
873            bubbles: null, // handled by global flag
874            navConfetti: null // handled by global flag
875        };
876
877        const elementId = effectMap[effectName];
878        
879        if (effectName === 'seasonalOverlay') {
880            // Handle seasonal overlay toggle
881            if (enabled) {
882                if (!window.seasonalOverlay && typeof SeasonalOverlay !== 'undefined') {
883                    window.seasonalOverlay = new SeasonalOverlay();
884                    emitDevLog('🎉 Seasonal overlay enabled');
885                }
886            } else {
887                if (window.seasonalOverlay && typeof window.seasonalOverlay.destroy === 'function') {
888                    window.seasonalOverlay.destroy();
889                    window.seasonalOverlay = null;
890                    emitDevLog('🎉 Seasonal overlay disabled');
891                }
892            }
893            return;
894        }
895        
896        if (effectName === 'weatherOverlay') {
897            // Handle weather overlay toggle
898            if (enabled) {
899                if (!window.weatherOverlay && typeof WeatherOverlay !== 'undefined') {
900                    window.weatherOverlay = new WeatherOverlay();
901                    emitDevLog('🌤️ Weather overlay enabled');
902                }
903            } else {
904                if (window.weatherOverlay && typeof window.weatherOverlay.destroy === 'function') {
905                    window.weatherOverlay.destroy();
906                    window.weatherOverlay = null;
907                    emitDevLog('🌤️ Weather overlay disabled');
908                }
909            }
910            return;
911        }
912        
913        if (effectName === 'bubbles') {
914            // Handle bubbles toggle via global flag
915            window.BUBBLES_DISABLED = !enabled;
916            emitDevLog('Bubbles:', enabled ? 'ON' : 'OFF');
917            return;
918        }
919        
920        if (effectName === 'navConfetti') {
921            // Handle nav confetti toggle via global flag
922            window.NAV_CONFETTI_DISABLED = !enabled;
923            emitDevLog('Nav Confetti:', enabled ? 'ON' : 'OFF');
924            return;
925        }
926        
927        if (effectName === 'shadows') {
928            // Toggle shadows via global flag
929            if (!window.SHADOWS_DISABLED) {
930                window.SHADOWS_DISABLED = !enabled;
931            } else {
932                window.SHADOWS_DISABLED = !enabled;
933            }
934            
935            // Also update fishBuddyShadows if it exists
936            if (window.fishBuddyShadows && window.fishBuddyShadows.setPerformanceMode) {
937                window.fishBuddyShadows.setPerformanceMode(!enabled);
938            }
939            
940            emitDevLog('Shadows:', enabled ? 'ON' : 'OFF');
941        } else if (effectName === 'clickSparks') {
942            window.CLICK_SPARKS_DISABLED = !enabled;
943            if (window.ClickSparks && typeof window.ClickSparks.setEnabled === 'function') {
944                window.ClickSparks.setEnabled(enabled);
945            }
946            emitDevLog('Click sparks:', enabled ? 'ON' : 'OFF');
947        } else if (elementId) {
948            const element = document.getElementById(elementId);
949            if (element) {
950                element.style.display = enabled ? '' : 'none';
951            }
952        }
953    }
954
955    function applyTrailColorsSetting(colors) {
956        var merged = colors;
957        if (window.PupwifeTrailColorSupport && typeof window.PupwifeTrailColorSupport.mergeTrailColors === 'function') {
958            merged = window.PupwifeTrailColorSupport.mergeTrailColors(colors);
959        }
960        currentSettings.trailColors = merged;
961        if (window.CursorTrails && typeof window.CursorTrails.setTrailColors === 'function') {
962            window.CursorTrails.setTrailColors(merged);
963        }
964    }
965
966    function toggleCursorTrail(trailType) {
967        if (window.CursorTrails && typeof window.CursorTrails.setTrailType === 'function') {
968            window.CursorTrails.setTrailType(trailType);
969        } else {
970            emitDevWarn('CursorTrails not loaded yet');
971        }
972    }
973
974    const TRAIL_AMOUNT_LABELS = ['Minimum', 'Low', 'Normal', 'High', 'Extreme'];
975
976    function parseTrailAmount(value, fallback) {
977        if (fallback === undefined) fallback = DEFAULT_SETTINGS.trailAmount;
978        const n = parseInt(value, 10);
979        if (Number.isNaN(n)) return fallback;
980        return Math.max(0, Math.min(4, n));
981    }
982
983    function toggleCursorTrailAmount(level) {
984        const amount = parseTrailAmount(level);
985        if (window.CursorTrails && typeof window.CursorTrails.setTrailAmount === 'function') {
986            window.CursorTrails.setTrailAmount(amount);
987        }
988    }
989
990    function updateTrailAmountLabel(panel) {
991        if (!panel) return;
992        const slider = panel.querySelector('#setting-trailAmount');
993        const label = panel.querySelector('#setting-trailAmount-label');
994        if (!slider || !label) return;
995        const v = parseTrailAmount(slider.value);
996        label.textContent = TRAIL_AMOUNT_LABELS[v];
997        slider.setAttribute('aria-valuetext', TRAIL_AMOUNT_LABELS[v]);
998    }
999
1000    function updateTrailControlsState() {
1001        const panel = document.getElementById('settings-panel');
1002        if (!panel) return;
1003        const amountDisabled = currentSettings.cursorTrail === 'none' || currentSettings.performanceMode;
1004        setCursorTrailPickerDisabled(!!currentSettings.performanceMode);
1005        const amountRow = panel.querySelector('#setting-trailAmount-row');
1006        if (amountRow) {
1007            amountRow.classList.toggle('is-disabled', amountDisabled);
1008        }
1009        const slider = panel.querySelector('#setting-trailAmount');
1010        if (slider) {
1011            slider.disabled = amountDisabled;
1012        }
1013    }
1014
1015    function scriptSrcMatches(el, src) {
1016        if (!el) return false;
1017        var attr = el.getAttribute('src') || '';
1018        if (attr === src) return true;
1019        try {
1020            return el.src && (el.src.endsWith(src) || el.src.indexOf(src.replace(/^\//, '')) !== -1);
1021        } catch (e) {
1022            return false;
1023        }
1024    }
1025
1026    function findScriptBySrc(src) {
1027        var scripts = document.querySelectorAll('script[src]');
1028        for (var i = 0; i < scripts.length; i++) {
1029            if (scriptSrcMatches(scripts[i], src)) return scripts[i];
1030        }
1031        return null;
1032    }
1033
1034    function loadScriptThenRun(src, onReady) {
1035        function run() {
1036            if (typeof onReady === 'function') onReady();
1037        }
1038
1039        if (src.indexOf('roughjsCursor.js') !== -1 && window.RoughJSCursor) {
1040            run();
1041            return;
1042        }
1043
1044        if (window.PupwifeScriptLoad && typeof window.PupwifeScriptLoad.once === 'function') {
1045            window.PupwifeScriptLoad.once(src, { markKey: 'pupwifeLoaded' }).then(run).catch(run);
1046            return;
1047        }
1048
1049        var existing = findScriptBySrc(src);
1050        if (existing) {
1051            if (existing.getAttribute('data-loaded') === '1') {
1052                run();
1053                return;
1054            }
1055            var finished = function () {
1056                existing.setAttribute('data-loaded', '1');
1057                run();
1058            };
1059            existing.addEventListener('load', finished, { once: true });
1060            existing.addEventListener('error', finished, { once: true });
1061            window.setTimeout(finished, 0);
1062            return;
1063        }
1064
1065        var script = document.createElement('script');
1066        script.src = src;
1067        script.onload = function () {
1068            script.setAttribute('data-loaded', '1');
1069            run();
1070        };
1071        script.onerror = function () {
1072            script.setAttribute('data-loaded', '1');
1073            run();
1074        };
1075        document.head.appendChild(script);
1076    }
1077
1078    function clearImageCustomCursor() {
1079        const existingStyle = document.getElementById('custom-cursor-style');
1080        if (existingStyle) {
1081            existingStyle.remove();
1082        }
1083    }
1084
1085    function applyCatPawArmCursor() {
1086        clearImageCustomCursor();
1087        if (window.TwoJSCursor && typeof window.TwoJSCursor.setCursor === 'function') {
1088            window.TwoJSCursor.setCursor('none');
1089        }
1090        if (window.RoughJSCursor && typeof window.RoughJSCursor.setCursor === 'function') {
1091            window.RoughJSCursor.setCursor('none');
1092        }
1093        if (currentSettings.performanceMode) {
1094            if (window.CatPawArmCursor && typeof window.CatPawArmCursor.setCursor === 'function') {
1095                window.CatPawArmCursor.setCursor('none');
1096            }
1097            return;
1098        }
1099        if (window.CatPawArmCursor && typeof window.CatPawArmCursor.setCursor === 'function') {
1100            window.CatPawArmCursor.setCursor('cat-paw-arm');
1101            return;
1102        }
1103        emitDevWarn('Stretchy paw cursor not loaded yet');
1104        loadScriptThenRun('/scripts/dependencies/cursors/catPawArmCursor.js', function () {
1105            if (window.CatPawArmCursor) {
1106                window.CatPawArmCursor.setCursor('cat-paw-arm');
1107            }
1108        });
1109    }
1110
1111    function applyRoughCursor(cursorType) {
1112        if (window.CatPawArmCursor && typeof window.CatPawArmCursor.setCursor === 'function') {
1113            window.CatPawArmCursor.setCursor('none');
1114        }
1115        clearImageCustomCursor();
1116        if (window.TwoJSCursor && typeof window.TwoJSCursor.setCursor === 'function') {
1117            window.TwoJSCursor.setCursor('none');
1118        }
1119        if (window.RoughJSCursor && typeof window.RoughJSCursor.setCursor === 'function') {
1120            window.RoughJSCursor.setCursor(cursorType);
1121            return;
1122        }
1123        emitDevWarn('Rough.js cursor system not loaded yet');
1124        loadScriptThenRun('/scripts/dependencies/cursors/roughjsCursor.js', () => {
1125            if (window.RoughJSCursor) {
1126                window.RoughJSCursor.setCursor(cursorType);
1127            }
1128        });
1129    }
1130
1131    function applyTwoJsCursor(cursorType) {
1132        if (window.CatPawArmCursor && typeof window.CatPawArmCursor.setCursor === 'function') {
1133            window.CatPawArmCursor.setCursor('none');
1134        }
1135        clearImageCustomCursor();
1136        if (window.RoughJSCursor && typeof window.RoughJSCursor.setCursor === 'function') {
1137            window.RoughJSCursor.setCursor('none');
1138        }
1139        if (window.TwoJSCursor && typeof window.TwoJSCursor.setCursor === 'function') {
1140            window.TwoJSCursor.setCursor(cursorType);
1141            return;
1142        }
1143        emitDevWarn('Two.js cursor system not loaded yet');
1144        loadScriptThenRun('/scripts/dependencies/cursors/twojsCursor.js', () => {
1145            if (window.TwoJSCursor) {
1146                window.TwoJSCursor.setCursor(cursorType);
1147            }
1148        });
1149    }
1150
1151    function clearEngineCursors() {
1152        if (window.CatPawArmCursor && typeof window.CatPawArmCursor.setCursor === 'function') {
1153            window.CatPawArmCursor.setCursor('none');
1154        }
1155        if (window.TwoJSCursor && typeof window.TwoJSCursor.setCursor === 'function') {
1156            window.TwoJSCursor.setCursor('none');
1157        }
1158        if (window.RoughJSCursor && typeof window.RoughJSCursor.setCursor === 'function') {
1159            window.RoughJSCursor.setCursor('none');
1160        }
1161    }
1162
1163    function toggleCustomCursor(cursorType) {
1164        // Handle stretchy paw cursor
1165        if (cursorType === 'cat-paw-arm') {
1166            applyCatPawArmCursor();
1167            updateCatPawPanelVisibility();
1168            return;
1169        }
1170
1171        updateCatPawPanelVisibility();
1172
1173        // Handle Rough.js cursors
1174        if (cursorType && cursorType.startsWith('rough-')) {
1175            applyRoughCursor(cursorType);
1176            return;
1177        }
1178
1179        // Handle Two.js cursors
1180        if (cursorType && cursorType.startsWith('twojs-')) {
1181            applyTwoJsCursor(cursorType);
1182            return;
1183        }
1184
1185        // Remove engine cursors if switching away
1186        clearEngineCursors();
1187
1188        // Remove existing custom cursor style if it exists
1189        clearImageCustomCursor();
1190
1191        if (cursorType === 'none') {
1192            // No custom cursor - use defaults
1193            return;
1194        }
1195
1196        // Create style element for custom cursors
1197        const style = document.createElement('style');
1198        style.id = 'custom-cursor-style';
1199        
1200        if (cursorType === 'asriel') {
1201            // Asriel cursor set
1202            style.textContent = `
1203                * {
1204                    cursor: url('/cursors/Asriel/cursor.webp'), auto !important;
1205                }
1206                a, button, [role="button"], input[type="button"], input[type="submit"], 
1207                input[type="reset"], [onclick], .clickable, [style*="cursor: pointer"] {
1208                    cursor: url('/cursors/Asriel/pointer.webp'), pointer !important;
1209                }
1210                input[type="text"], input[type="password"], input[type="email"], 
1211                input[type="search"], input[type="url"], input[type="tel"], 
1212                textarea, [contenteditable="true"], [contenteditable], 
1213                [style*="cursor: text"] {
1214                    cursor: url('/cursors/Asriel/text.webp'), text !important;
1215                }
1216                [style*="cursor: wait"], .loading, [aria-busy="true"] {
1217                    cursor: url('/cursors/Asriel/wait.webp'), wait !important;
1218                }
1219                [disabled], [style*="cursor: not-allowed"], 
1220                [aria-disabled="true"] {
1221                    cursor: url('/cursors/Asriel/not-allowed.webp'), not-allowed !important;
1222                }
1223            `;
1224        } else if (cursorType === 'axolotl') {
1225            // Axolotl cursor set - using available files with fallbacks
1226            style.textContent = `
1227                * {
1228                    cursor: url('/cursors/Axolotl/normal-select.webp'), auto !important;
1229                }
1230                a, button, [role="button"], input[type="button"], input[type="submit"], 
1231                input[type="reset"], [onclick], .clickable, [style*="cursor: pointer"] {
1232                    cursor: url('/cursors/Axolotl/link-select.webp'), pointer !important;
1233                }
1234                input[type="text"], input[type="password"], input[type="email"], 
1235                input[type="search"], input[type="url"], input[type="tel"], 
1236                textarea, [contenteditable="true"], [contenteditable], 
1237                [style*="cursor: text"] {
1238                    cursor: url('/cursors/Axolotl/text-select.webp'), text !important;
1239                }
1240                [style*="cursor: wait"], .loading, [aria-busy="true"] {
1241                    cursor: url('/cursors/Axolotl/busy.webp'), wait !important;
1242                }
1243                [disabled], [style*="cursor: not-allowed"], 
1244                [aria-disabled="true"] {
1245                    cursor: url('/cursors/Axolotl/unavailable.webp'), not-allowed !important;
1246                }
1247                [style*="cursor: move"], [draggable="true"] {
1248                    cursor: url('/cursors/Axolotl/move.webp'), move !important;
1249                }
1250                [style*="cursor: crosshair"] {
1251                    cursor: url('/cursors/Axolotl/precision-select.webp'), crosshair !important;
1252                }
1253                [style*="cursor: help"] {
1254                    cursor: url('/cursors/Axolotl/normal-select.webp'), help !important;
1255                }
1256                [style*="cursor: ew-resize"], [style*="cursor: col-resize"] {
1257                    cursor: url('/cursors/Axolotl/move.webp'), ew-resize !important;
1258                }
1259                [style*="cursor: ns-resize"], [style*="cursor: row-resize"] {
1260                    cursor: url('/cursors/Axolotl/move.webp'), ns-resize !important;
1261                }
1262                [style*="cursor: nwse-resize"], [style*="cursor: nw-resize"], [style*="cursor: se-resize"] {
1263                    cursor: url('/cursors/Axolotl/move.webp'), nwse-resize !important;
1264                }
1265                [style*="cursor: nesw-resize"], [style*="cursor: ne-resize"], [style*="cursor: sw-resize"] {
1266                    cursor: url('/cursors/Axolotl/move.webp'), nesw-resize !important;
1267                }
1268                [style*="cursor: alias"] {
1269                    cursor: url('/cursors/Axolotl/link-select.webp'), alias !important;
1270                }
1271                [style*="cursor: context-menu"] {
1272                    cursor: url('/cursors/Axolotl/alternate-select.webp'), context-menu !important;
1273                }
1274            `;
1275        }
1276        
1277        document.head.appendChild(style);
1278        emitDevLog(`🎯 Custom cursor set to: ${cursorType}`);
1279    }
1280
1281    // ====================================
1282    // UI CREATION
1283    // ====================================
1284
1285    function ensureSettingsAssets() {
1286        if (isCitrusOsPage()) {
1287            return;
1288        }
1289        if (!document.querySelector('link[href*="settings-menu.css"]')) {
1290            var css = document.createElement('link');
1291            css.rel = 'stylesheet';
1292            css.href = '/css/settings-menu.css';
1293            css.type = 'text/css';
1294            document.head.appendChild(css);
1295        }
1296    }
1297
1298    function ensureTrailPickerScript() {
1299        return new Promise(function (resolve) {
1300            if (window.PupwifeTrailPicker) {
1301                resolve();
1302                return;
1303            }
1304            var existing = document.querySelector('script[data-trail-picker]');
1305            if (existing) {
1306                if (existing.getAttribute('data-trail-picker-ready') === '1') {
1307                    resolve();
1308                    return;
1309                }
1310                existing.addEventListener('load', resolve, { once: true });
1311                existing.addEventListener('error', resolve, { once: true });
1312                return;
1313            }
1314            var script = document.createElement('script');
1315            script.src = '/scripts/effects/trailPicker.js';
1316            script.setAttribute('data-trail-picker', '1');
1317            script.onload = script.onerror = function () {
1318                script.setAttribute('data-trail-picker-ready', '1');
1319                resolve();
1320            };
1321            document.head.appendChild(script);
1322        });
1323    }
1324
1325    function mountCursorTrailPicker(panel) {
1326        var mountEl = panel.querySelector('#setting-cursorTrail-picker');
1327        if (!mountEl || !window.PupwifeTrailPicker) return;
1328        window.PupwifeTrailPicker.mount(mountEl, {
1329            value: currentSettings.cursorTrail,
1330            disabled: !!currentSettings.performanceMode,
1331            getFavorites: function () {
1332                return currentSettings.trailFavorites || [];
1333            },
1334            onFavoriteChange: function (favs) {
1335                setSetting('trailFavorites', favs.slice());
1336                if (typeof window.PupwifeTrailPicker.refreshFavorites === 'function') {
1337                    window.PupwifeTrailPicker.refreshFavorites();
1338                }
1339            },
1340            onChange: function (value) {
1341                setSetting('cursorTrail', value);
1342                updateTrailControlsState();
1343                updateTrailColorPanelVisibility();
1344                if (window.PupwifeSettingsDiscoverability) {
1345                    window.PupwifeSettingsDiscoverability.markSeen('trail', value);
1346                }
1347            }
1348        });
1349    }
1350
1351    function setCursorTrailPickerValue(value) {
1352        if (window.PupwifeTrailPicker && typeof window.PupwifeTrailPicker.setValue === 'function') {
1353            window.PupwifeTrailPicker.setValue(value);
1354        }
1355    }
1356
1357    function setCursorTrailPickerDisabled(disabled) {
1358        if (window.PupwifeTrailPicker && typeof window.PupwifeTrailPicker.setDisabled === 'function') {
1359            window.PupwifeTrailPicker.setDisabled(disabled);
1360        }
1361    }
1362
1363    function ensureCursorPickerScript() {
1364        return loadScriptOnce('/scripts/effects/settingsBubbleSelect.js', 'bubble-select')
1365            .then(function () {
1366                return new Promise(function (resolve) {
1367                    if (window.PupwifeCursorPicker) {
1368                        resolve();
1369                        return;
1370                    }
1371                    var existing = document.querySelector('script[data-cursor-picker]');
1372                    if (existing) {
1373                        if (existing.getAttribute('data-cursor-picker-ready') === '1') {
1374                            resolve();
1375                            return;
1376                        }
1377                        existing.addEventListener('load', resolve, { once: true });
1378                        existing.addEventListener('error', resolve, { once: true });
1379                        return;
1380                    }
1381                    var script = document.createElement('script');
1382                    script.src = '/scripts/effects/cursorPicker.js';
1383                    script.setAttribute('data-cursor-picker', '1');
1384                    script.onload = script.onerror = function () {
1385                        script.setAttribute('data-cursor-picker-ready', '1');
1386                        resolve();
1387                    };
1388                    document.head.appendChild(script);
1389                });
1390            });
1391    }
1392
1393    function mountCustomCursorPicker(panel) {
1394        var mountEl = panel.querySelector('#setting-customCursor-picker');
1395        if (!mountEl || !window.PupwifeCursorPicker) return;
1396        window.PupwifeCursorPicker.mount(mountEl, {
1397            value: currentSettings.customCursor,
1398            disabled: false,
1399            onChange: function (value) {
1400                setSetting('customCursor', value);
1401                updateCatPawPanelVisibility();
1402            }
1403        });
1404    }
1405
1406    function setCustomCursorPickerValue(value) {
1407        if (window.PupwifeCursorPicker && typeof window.PupwifeCursorPicker.setValue === 'function') {
1408            window.PupwifeCursorPicker.setValue(value);
1409        }
1410    }
1411
1412    function setCustomCursorPickerDisabled(disabled) {
1413        if (window.PupwifeCursorPicker && typeof window.PupwifeCursorPicker.setDisabled === 'function') {
1414            window.PupwifeCursorPicker.setDisabled(disabled);
1415        }
1416    }
1417
1418    function ensurePalettePickerScript() {
1419        return loadScriptOnce('/scripts/effects/siteThemePalettes.js', 'site-theme-palettes')
1420            .then(function () {
1421                return loadScriptOnce('/scripts/effects/settingsBubbleSelect.js', 'bubble-select');
1422            })
1423            .then(function () {
1424                return new Promise(function (resolve) {
1425                    if (window.PupwifePalettePicker) {
1426                        resolve();
1427                        return;
1428                    }
1429                    var existing = document.querySelector('script[data-palette-picker]');
1430                    if (existing) {
1431                        if (existing.getAttribute('data-palette-picker-ready') === '1') {
1432                            resolve();
1433                            return;
1434                        }
1435                        existing.addEventListener('load', resolve, { once: true });
1436                        existing.addEventListener('error', resolve, { once: true });
1437                        return;
1438                    }
1439                    var script = document.createElement('script');
1440                    script.src = '/scripts/effects/palettePicker.js';
1441                    script.setAttribute('data-palette-picker', '1');
1442                    script.onload = script.onerror = function () {
1443                        script.setAttribute('data-palette-picker-ready', '1');
1444                        resolve();
1445                    };
1446                    document.head.appendChild(script);
1447                });
1448            });
1449    }
1450
1451    function mountPalettePicker(panel) {
1452        var mountEl = panel.querySelector('#setting-palette-picker');
1453        if (!mountEl || !window.PupwifePalettePicker) return;
1454        var palette = currentSettings.palette || DEFAULT_SETTINGS.palette;
1455        if (window.SiteThemeToggle && typeof window.SiteThemeToggle.readPaletteSetting === 'function') {
1456            palette = window.SiteThemeToggle.readPaletteSetting();
1457        }
1458        window.PupwifePalettePicker.mount(mountEl, {
1459            value: palette,
1460            onChange: handlePaletteChange
1461        });
1462    }
1463
1464    function setPalettePickerValue(value) {
1465        if (window.PupwifePalettePicker && typeof window.PupwifePalettePicker.setValue === 'function') {
1466            window.PupwifePalettePicker.setValue(value);
1467        }
1468    }
1469
1470    function ensureBackgroundPickerScript() {
1471        return loadScriptOnce('/scripts/effects/siteBackgrounds.js', 'site-backgrounds')
1472            .then(function () {
1473                return loadScriptOnce('/scripts/effects/siteBackgroundApply.js', 'site-background-apply');
1474            })
1475            .then(function () {
1476                return loadScriptOnce('/scripts/effects/settingsBubbleSelect.js', 'bubble-select');
1477            })
1478            .then(function () {
1479                return new Promise(function (resolve) {
1480                    if (window.PupwifeBackgroundPicker) {
1481                        resolve();
1482                        return;
1483                    }
1484                    var existing = document.querySelector('script[data-background-picker]');
1485                    if (existing) {
1486                        if (existing.getAttribute('data-background-picker-ready') === '1') {
1487                            resolve();
1488                            return;
1489                        }
1490                        existing.addEventListener('load', resolve, { once: true });
1491                        existing.addEventListener('error', resolve, { once: true });
1492                        return;
1493                    }
1494                    var script = document.createElement('script');
1495                    script.src = '/scripts/effects/backgroundPicker.js';
1496                    script.setAttribute('data-background-picker', '1');
1497                    script.onload = script.onerror = function () {
1498                        script.setAttribute('data-background-picker-ready', '1');
1499                        resolve();
1500                    };
1501                    document.head.appendChild(script);
1502                });
1503            })
1504            .then(function () {
1505                return loadScriptOnce('/scripts/effects/backgroundTuning.js', 'background-tuning');
1506            });
1507    }
1508
1509    var backgroundTuningUi = null;
1510
1511    function mountBackgroundTuning(panel) {
1512        if (!window.SiteBackgroundTuning) return;
1513        var host = panel.querySelector('#setting-background-tuning-body');
1514        if (!host) return;
1515        window.SiteBackgroundTuning.bindCollapsible(panel);
1516        backgroundTuningUi = window.SiteBackgroundTuning.mount(host, {
1517            tuning: currentSettings.backgroundTuning,
1518            activeBackground: currentSettings.background || DEFAULT_SETTINGS.background,
1519            onChange: function (tuning, changedPresetId) {
1520                currentSettings.backgroundTuning = window.SiteBackgroundTuning.saveTuning(tuning, changedPresetId);
1521            }
1522        });
1523    }
1524
1525    function setBackgroundTuningActive(id) {
1526        if (backgroundTuningUi && typeof backgroundTuningUi.setActiveBackground === 'function') {
1527            backgroundTuningUi.setActiveBackground(id);
1528        }
1529    }
1530
1531    function updateCloudsBackgroundWarning() {
1532        var panel = document.getElementById('settings-panel');
1533        if (!panel) return;
1534        var warning = panel.querySelector('#setting-background-clouds-warning');
1535        if (!warning) return;
1536        var bg = currentSettings.background || DEFAULT_SETTINGS.background;
1537        var wantsClouds = bg === 'clouds';
1538        var unsupported = window.SiteBackgroundApply &&
1539            typeof window.SiteBackgroundApply.webgl2Supported === 'function' &&
1540            !window.SiteBackgroundApply.webgl2Supported();
1541        var fallback = window.SiteBackgroundApply &&
1542            typeof window.SiteBackgroundApply.isCloudsFallbackActive === 'function' &&
1543            window.SiteBackgroundApply.isCloudsFallbackActive();
1544        warning.style.display = (wantsClouds && (unsupported || fallback)) ? '' : 'none';
1545    }
1546
1547    function mountBackgroundPicker(panel) {
1548        var mountEl = panel.querySelector('#setting-background-picker');
1549        if (!mountEl || !window.PupwifeBackgroundPicker) return;
1550        window.PupwifeBackgroundPicker.mount(mountEl, {
1551            value: currentSettings.background || DEFAULT_SETTINGS.background,
1552            disabled: false,
1553            onChange: function (value) {
1554                setSetting('background', value);
1555                setBackgroundTuningActive(value);
1556                updateCloudsBackgroundWarning();
1557            }
1558        });
1559        updateCloudsBackgroundWarning();
1560    }
1561
1562    function setBackgroundPickerValue(value) {
1563        if (window.PupwifeBackgroundPicker && typeof window.PupwifeBackgroundPicker.setValue === 'function') {
1564            window.PupwifeBackgroundPicker.setValue(value);
1565        }
1566    }
1567
1568    function handlePaletteChange(value) {
1569        currentSettings.palette = value;
1570        if (window.SiteThemeToggle && typeof window.SiteThemeToggle.setPalette === 'function') {
1571            window.SiteThemeToggle.setPalette(value, 'settings');
1572        } else if (window.SiteThemePalettes && typeof window.SiteThemePalettes.writeMergedThemeSettings === 'function') {
1573            window.SiteThemePalettes.writeMergedThemeSettings({ palette: value });
1574        } else {
1575            setSetting('palette', value);
1576            return;
1577        }
1578        saveSettings();
1579        emitDevLog('Color palette: ' + value);
1580    }
1581
1582    function loadScriptOnce(src, attr) {
1583        return new Promise(function (resolve) {
1584            if (attr === 'site-backgrounds' && window.SiteBackgrounds) {
1585                resolve();
1586                return;
1587            }
1588            if (attr === 'site-background-apply' && window.SiteBackgroundApply) {
1589                resolve();
1590                return;
1591            }
1592            if (attr === 'site-theme-palettes' && window.SiteThemePalettes) {
1593                resolve();
1594                return;
1595            }
1596            if (attr === 'bubble-select' && window.PupwifeSettingsBubbleSelect) {
1597                resolve();
1598                return;
1599            }
1600            if (attr === 'background-tuning' && window.SiteBackgroundTuning) {
1601                resolve();
1602                return;
1603            }
1604            if (attr === 'color-picker' && window.PupwifeSettingsColorPicker) {
1605                resolve();
1606                return;
1607            }
1608            if (attr === 'woim-color-panel' && window.PupwifeWoimColorPanel) {
1609                resolve();
1610                return;
1611            }
1612            if (attr === 'discoverability' && window.PupwifeSettingsDiscoverability) {
1613                resolve();
1614                return;
1615            }
1616            if (attr === 'trail-colors' && window.PupwifeTrailColorSupport) {
1617                resolve();
1618                return;
1619            }
1620            if (attr === 'search' && window.PupwifeSettingsSearch) {
1621                resolve();
1622                return;
1623            }
1624            if (attr === 'export-import' && window.PupwifeSettingsExportImport) {
1625                resolve();
1626                return;
1627            }
1628            var existing = document.querySelector('script[data-settings-' + attr + ']');
1629            if (existing) {
1630                existing.addEventListener('load', resolve, { once: true });
1631                existing.addEventListener('error', resolve, { once: true });
1632                return;
1633            }
1634            var script = document.createElement('script');
1635            script.src = src;
1636            script.setAttribute('data-settings-' + attr, '1');
1637            script.onload = script.onerror = resolve;
1638            document.head.appendChild(script);
1639        });
1640    }
1641
1642    function ensureWoimColorPanelScript() {
1643        return loadScriptOnce('/scripts/effects/settingsBubbleSelect.js', 'bubble-select')
1644            .then(function () {
1645                return loadScriptOnce('/scripts/effects/settingsColorPicker.js', 'color-picker');
1646            })
1647            .then(function () {
1648                return loadScriptOnce('/scripts/effects/woimColorPanel.js', 'woim-color-panel');
1649            });
1650    }
1651
1652    function updateWoimPanelVisibility(followerType) {
1653        var panel = document.getElementById('woim-string-settings');
1654        if (panel) {
1655            panel.classList.toggle('is-visible', followerType === 'woimString');
1656        }
1657    }
1658
1659    function bindWoimCollapsible(panel) {
1660        var woimPanel = panel.querySelector('#woim-string-settings');
1661        var toggle = panel.querySelector('#woim-string-settings-toggle');
1662        var body = panel.querySelector('#woim-string-settings-body');
1663        if (!woimPanel || !toggle || !body || woimPanel.dataset.collapseBound === '1') return;
1664        woimPanel.dataset.collapseBound = '1';
1665
1666        var collapsed = true;
1667        try {
1668            var stored = localStorage.getItem('pupwife_woim_settings_collapsed');
1669            if (stored === '0') collapsed = false;
1670            else if (stored === '1') collapsed = true;
1671        } catch (e) { /* ignore */ }
1672
1673        function applyCollapsed(state) {
1674            collapsed = !!state;
1675            woimPanel.classList.toggle('is-collapsed', collapsed);
1676            toggle.setAttribute('aria-expanded', collapsed ? 'false' : 'true');
1677            try {
1678                localStorage.setItem('pupwife_woim_settings_collapsed', collapsed ? '1' : '0');
1679            } catch (e) { /* ignore */ }
1680        }
1681
1682        applyCollapsed(collapsed);
1683
1684        toggle.addEventListener('click', function () {
1685            applyCollapsed(!collapsed);
1686        });
1687    }
1688
1689    function bindWoimSlider(panel, inputId, labelId, key, formatLabel) {
1690        var input = panel.querySelector('#' + inputId);
1691        var label = panel.querySelector('#' + labelId);
1692        if (!input) return;
1693
1694        function readValue() {
1695            return parseWoimNotch(input.value, parseInt(input.min, 10), parseInt(input.max, 10), input.value);
1696        }
1697
1698        function applyLive(v) {
1699            if (window.WoimStringFollower && typeof window.WoimStringFollower.applySettings === 'function') {
1700                var live = mergeWoimString(currentSettings.woimString);
1701                live[key] = v;
1702                window.WoimStringFollower.applySettings(live);
1703            }
1704        }
1705
1706        input.addEventListener('input', function () {
1707            var v = readValue();
1708            applyLive(v);
1709            if (label) label.textContent = formatLabel ? formatLabel(v) : String(v);
1710        });
1711        input.addEventListener('change', function () {
1712            var v = readValue();
1713            input.value = String(v);
1714            var patch = {};
1715            patch[key] = v;
1716            setWoimString(patch);
1717            if (label) label.textContent = formatLabel ? formatLabel(v) : String(v);
1718        });
1719    }
1720
1721    function syncWoimSettingsUI(panel) {
1722        if (!panel) return;
1723        var w = mergeWoimString(currentSettings.woimString);
1724        var map = [
1725            ['setting-woim-ropePoints', 'setting-woim-ropePoints-label', w.ropePoints, null],
1726            ['setting-woim-bodyPoints', 'setting-woim-bodyPoints-label', w.bodyPoints, null],
1727            ['setting-woim-size', 'setting-woim-size-label', w.size, function (v) { return v + '%'; }],
1728            ['setting-woim-ropeLength', 'setting-woim-ropeLength-label', w.ropeLength, function (v) { return v + '%'; }],
1729            ['setting-woim-ropeThickness', 'setting-woim-ropeThickness-label', w.ropeThickness, function (v) { return v + '%'; }],
1730            ['setting-woim-anchorSize', 'setting-woim-anchorSize-label', w.anchorSize, function (v) { return v + '%'; }],
1731            ['setting-woim-anchorThickness', 'setting-woim-anchorThickness-label', w.anchorThickness, function (v) { return v + '%'; }]
1732        ];
1733        map.forEach(function (row) {
1734            var input = panel.querySelector('#' + row[0]);
1735            var label = panel.querySelector('#' + row[1]);
1736            if (input) input.value = String(row[2]);
1737            if (label) label.textContent = row[3] ? row[3](row[2]) : String(row[2]);
1738        });
1739        var anchorShow = panel.querySelector('#setting-woim-anchorShow');
1740        if (anchorShow) {
1741            anchorShow.checked = w.anchorShow === true;
1742        }
1743        if (woimColorPanels.rope) woimColorPanels.rope.setValue(w.rope);
1744        if (woimColorPanels.woim) woimColorPanels.woim.setValue(w.woim);
1745        updateWoimPanelVisibility(currentSettings.follower);
1746    }
1747
1748    function mountWoimSettingsPanel(panel) {
1749        if (!panel || !window.PupwifeWoimColorPanel) return;
1750        var woimPanel = panel.querySelector('#woim-string-settings');
1751        if (!woimPanel || woimPanel.dataset.mounted === '1') {
1752            syncWoimSettingsUI(panel);
1753            return;
1754        }
1755        woimPanel.dataset.mounted = '1';
1756
1757        bindWoimCollapsible(panel);
1758
1759        bindWoimSlider(panel, 'setting-woim-ropePoints', 'setting-woim-ropePoints-label', 'ropePoints');
1760        bindWoimSlider(panel, 'setting-woim-bodyPoints', 'setting-woim-bodyPoints-label', 'bodyPoints');
1761        bindWoimSlider(panel, 'setting-woim-size', 'setting-woim-size-label', 'size', function (v) { return v + '%'; });
1762        bindWoimSlider(panel, 'setting-woim-ropeLength', 'setting-woim-ropeLength-label', 'ropeLength', function (v) { return v + '%'; });
1763        bindWoimSlider(panel, 'setting-woim-ropeThickness', 'setting-woim-ropeThickness-label', 'ropeThickness', function (v) { return v + '%'; });
1764        bindWoimSlider(panel, 'setting-woim-anchorSize', 'setting-woim-anchorSize-label', 'anchorSize', function (v) { return v + '%'; });
1765        bindWoimSlider(panel, 'setting-woim-anchorThickness', 'setting-woim-anchorThickness-label', 'anchorThickness', function (v) { return v + '%'; });
1766
1767        var anchorShow = panel.querySelector('#setting-woim-anchorShow');
1768        if (anchorShow) {
1769            anchorShow.addEventListener('change', function () {
1770                setWoimString({ anchorShow: !!anchorShow.checked });
1771            });
1772        }
1773
1774        var w = mergeWoimString(currentSettings.woimString);
1775        var ropeMount = panel.querySelector('#woim-rope-color-mount');
1776        var bodyMount = panel.querySelector('#woim-body-color-mount');
1777        if (ropeMount) {
1778            woimColorPanels.rope = window.PupwifeWoimColorPanel.mount(ropeMount, {
1779                idPrefix: 'woim-rope-color',
1780                value: w.rope,
1781                onChange: function (val) { setWoimString({ rope: val }); }
1782            });
1783        }
1784        if (bodyMount) {
1785            woimColorPanels.woim = window.PupwifeWoimColorPanel.mount(bodyMount, {
1786                idPrefix: 'woim-body-color',
1787                value: w.woim,
1788                onChange: function (val) { setWoimString({ woim: val }); }
1789            });
1790        }
1791
1792        syncWoimSettingsUI(panel);
1793    }
1794
1795    function updateJellypuffPanelVisibility(followerType) {
1796        var panel = document.getElementById('jellypuff-settings');
1797        if (panel) {
1798            panel.classList.toggle('is-visible', followerType === 'jellypuff');
1799        }
1800    }
1801
1802    function bindJellypuffCollapsible(panel) {
1803        var jellyPanel = panel.querySelector('#jellypuff-settings');
1804        var toggle = panel.querySelector('#jellypuff-settings-toggle');
1805        var body = panel.querySelector('#jellypuff-settings-body');
1806        if (!jellyPanel || !toggle || !body || jellyPanel.dataset.collapseBound === '1') return;
1807        jellyPanel.dataset.collapseBound = '1';
1808
1809        var collapsed = true;
1810        try {
1811            var stored = localStorage.getItem('pupwife_jellypuff_settings_collapsed');
1812            if (stored === '0') collapsed = false;
1813            else if (stored === '1') collapsed = true;
1814        } catch (e) { /* ignore */ }
1815
1816        function applyCollapsed(state) {
1817            collapsed = !!state;
1818            jellyPanel.classList.toggle('is-collapsed', collapsed);
1819            toggle.setAttribute('aria-expanded', collapsed ? 'false' : 'true');
1820            try {
1821                localStorage.setItem('pupwife_jellypuff_settings_collapsed', collapsed ? '1' : '0');
1822            } catch (e) { /* ignore */ }
1823        }
1824
1825        applyCollapsed(collapsed);
1826
1827        toggle.addEventListener('click', function () {
1828            applyCollapsed(!collapsed);
1829        });
1830    }
1831
1832    function bindJellypuffSlider(panel, inputId, labelId, key, formatLabel) {
1833        var input = panel.querySelector('#' + inputId);
1834        var label = panel.querySelector('#' + labelId);
1835        if (!input) return;
1836
1837        function readValue() {
1838            return parseWoimNotch(input.value, parseInt(input.min, 10), parseInt(input.max, 10), input.value);
1839        }
1840
1841        function applyLive(v) {
1842            if (window.JellypuffFollower && typeof window.JellypuffFollower.applySettings === 'function') {
1843                var live = mergeJellypuff(currentSettings.jellypuff);
1844                live[key] = v;
1845                window.JellypuffFollower.applySettings(live);
1846            }
1847        }
1848
1849        input.addEventListener('input', function () {
1850            var v = readValue();
1851            applyLive(v);
1852            if (label) label.textContent = formatLabel ? formatLabel(v) : String(v);
1853        });
1854        input.addEventListener('change', function () {
1855            var v = readValue();
1856            input.value = String(v);
1857            var patch = {};
1858            patch[key] = v;
1859            setJellypuff(patch);
1860            if (label) label.textContent = formatLabel ? formatLabel(v) : String(v);
1861        });
1862    }
1863
1864    function syncJellypuffSettingsUI(panel) {
1865        if (!panel) return;
1866        var j = mergeJellypuff(currentSettings.jellypuff);
1867        var map = [
1868            ['setting-jellypuff-size', 'setting-jellypuff-size-label', j.size, function (v) { return v + '%'; }],
1869            ['setting-jellypuff-pulseRate', 'setting-jellypuff-pulseRate-label', j.pulseRate, function (v) { return v + '%'; }],
1870            ['setting-jellypuff-tentacleDensity', 'setting-jellypuff-tentacleDensity-label', j.tentacleDensity, function (v) { return v + '%'; }],
1871            ['setting-jellypuff-glowStrength', 'setting-jellypuff-glowStrength-label', j.glowStrength, function (v) { return v + '%'; }]
1872        ];
1873        map.forEach(function (row) {
1874            var input = panel.querySelector('#' + row[0]);
1875            var label = panel.querySelector('#' + row[1]);
1876            if (input) input.value = String(row[2]);
1877            if (label) label.textContent = row[3] ? row[3](row[2]) : String(row[2]);
1878        });
1879        if (jellyColorPanels.bell) jellyColorPanels.bell.setValue(j.bell);
1880        if (jellyColorPanels.tentacles) jellyColorPanels.tentacles.setValue(j.tentacles);
1881        updateJellypuffPanelVisibility(currentSettings.follower);
1882    }
1883
1884    function mountJellypuffSettingsPanel(panel) {
1885        if (!panel || !window.PupwifeWoimColorPanel) return;
1886        var jellyPanel = panel.querySelector('#jellypuff-settings');
1887        if (!jellyPanel || jellyPanel.dataset.mounted === '1') {
1888            syncJellypuffSettingsUI(panel);
1889            return;
1890        }
1891        jellyPanel.dataset.mounted = '1';
1892
1893        bindJellypuffCollapsible(panel);
1894
1895        bindJellypuffSlider(panel, 'setting-jellypuff-size', 'setting-jellypuff-size-label', 'size', function (v) { return v + '%'; });
1896        bindJellypuffSlider(panel, 'setting-jellypuff-pulseRate', 'setting-jellypuff-pulseRate-label', 'pulseRate', function (v) { return v + '%'; });
1897        bindJellypuffSlider(panel, 'setting-jellypuff-tentacleDensity', 'setting-jellypuff-tentacleDensity-label', 'tentacleDensity', function (v) { return v + '%'; });
1898        bindJellypuffSlider(panel, 'setting-jellypuff-glowStrength', 'setting-jellypuff-glowStrength-label', 'glowStrength', function (v) { return v + '%'; });
1899
1900        var j = mergeJellypuff(currentSettings.jellypuff);
1901        var bellMount = panel.querySelector('#jellypuff-bell-color-mount');
1902        var tentMount = panel.querySelector('#jellypuff-tentacles-color-mount');
1903        if (bellMount) {
1904            jellyColorPanels.bell = window.PupwifeWoimColorPanel.mount(bellMount, {
1905                idPrefix: 'jellypuff-bell-color',
1906                value: j.bell,
1907                onChange: function (val) { setJellypuff({ bell: val }); }
1908            });
1909        }
1910        if (tentMount) {
1911            jellyColorPanels.tentacles = window.PupwifeWoimColorPanel.mount(tentMount, {
1912                idPrefix: 'jellypuff-tentacles-color',
1913                value: j.tentacles,
1914                onChange: function (val) { setJellypuff({ tentacles: val }); }
1915            });
1916        }
1917
1918        syncJellypuffSettingsUI(panel);
1919    }
1920
1921    function updateCatPawPanelVisibility() {
1922        var panel = document.getElementById('cat-paw-arm-settings');
1923        if (panel) {
1924            panel.classList.toggle('is-visible', currentSettings.customCursor === 'cat-paw-arm');
1925        }
1926    }
1927
1928    function bindCatPawCollapsible(panel) {
1929        var pawPanel = panel.querySelector('#cat-paw-arm-settings');
1930        var toggle = panel.querySelector('#cat-paw-arm-settings-toggle');
1931        if (!pawPanel || !toggle || pawPanel.dataset.collapseBound === '1') return;
1932        pawPanel.dataset.collapseBound = '1';
1933
1934        var collapsed = true;
1935        try {
1936            var stored = localStorage.getItem('pupwife_cat_paw_settings_collapsed');
1937            if (stored === '0') collapsed = false;
1938            else if (stored === '1') collapsed = true;
1939        } catch (e) { /* ignore */ }
1940
1941        function applyCollapsed(state) {
1942            collapsed = !!state;
1943            pawPanel.classList.toggle('is-collapsed', collapsed);
1944            toggle.setAttribute('aria-expanded', collapsed ? 'false' : 'true');
1945            try {
1946                localStorage.setItem('pupwife_cat_paw_settings_collapsed', collapsed ? '1' : '0');
1947            } catch (e) { /* ignore */ }
1948        }
1949
1950        applyCollapsed(collapsed);
1951        toggle.addEventListener('click', function () {
1952            applyCollapsed(!collapsed);
1953        });
1954    }
1955
1956    function bindCatPawSlider(panel, inputId, labelId, key, formatLabel) {
1957        var input = panel.querySelector('#' + inputId);
1958        var label = panel.querySelector('#' + labelId);
1959        if (!input) return;
1960
1961        function readValue() {
1962            var min = parseFloat(input.min);
1963            var max = parseFloat(input.max);
1964            var n = parseFloat(input.value);
1965            if (Number.isNaN(n)) return input.value;
1966            if (min < 0 || String(input.step).indexOf('.') >= 0) {
1967                return Math.max(min, Math.min(max, n));
1968            }
1969            return parseWoimNotch(input.value, min, max, input.value);
1970        }
1971
1972        function applyLive(v) {
1973            if (window.CatPawArmCursor && typeof window.CatPawArmCursor.applySettings === 'function') {
1974                var live = mergeCatPawArm(currentSettings.catPawArm);
1975                live[key] = v;
1976                if (key === 'pawSize') live.armThickness = v;
1977                window.CatPawArmCursor.applySettings(live);
1978            }
1979        }
1980
1981        input.addEventListener('input', function () {
1982            var v = readValue();
1983            applyLive(v);
1984            if (label) label.textContent = formatLabel ? formatLabel(v) : String(v);
1985        });
1986        input.addEventListener('change', function () {
1987            var v = readValue();
1988            input.value = String(v);
1989            var patch = {};
1990            patch[key] = v;
1991            if (key === 'pawSize') {
1992                patch.armThickness = v;
1993            }
1994            if (key === 'originX' || key === 'originY') {
1995                patch.anchorPreset = 'custom';
1996            }
1997            setCatPawArm(patch);
1998            if (label) label.textContent = formatLabel ? formatLabel(v) : String(v);
1999            if (key === 'originX' || key === 'originY') {
2000                if (catPawAnchorSelect) catPawAnchorSelect.setValue('custom');
2001            }
2002        });
2003    }
2004
2005    function syncCatPawSettingsUI(panel) {
2006        if (!panel) return;
2007        var c = mergeCatPawArm(currentSettings.catPawArm);
2008        var map = [
2009            ['setting-catpaw-originX', 'setting-catpaw-originX-label', c.originX, function (v) { return v + '%'; }],
2010            ['setting-catpaw-originY', 'setting-catpaw-originY-label', c.originY, function (v) { return v + '%'; }],
2011            ['setting-catpaw-armTaper', 'setting-catpaw-armTaper-label', c.armTaper, function (v) { return v + '%'; }],
2012            ['setting-catpaw-armBend', 'setting-catpaw-armBend-label', c.armBend, function (v) { return v + '%'; }],
2013            ['setting-catpaw-followLag', 'setting-catpaw-followLag-label', c.followLag, function (v) { return v + '%'; }],
2014            ['setting-catpaw-pawSize', 'setting-catpaw-pawSize-label', c.pawSize, function (v) { return v + '%'; }],
2015            ['setting-catpaw-padSize', 'setting-catpaw-padSize-label', c.padSize, function (v) { return v + '%'; }],
2016            ['setting-catpaw-printSize', 'setting-catpaw-printSize-label', c.printSize, function (v) { return v + '%'; }],
2017            ['setting-catpaw-printFade', 'setting-catpaw-printFade-label', c.printFade, function (v) { return v + '%'; }],
2018            ['setting-catpaw-printMax', 'setting-catpaw-printMax-label', c.printMax, null],
2019            ['setting-catpaw-outlineThickness', 'setting-catpaw-outlineThickness-label', c.outlineThickness, function (v) { return v + '%'; }]
2020        ];
2021        map.forEach(function (row) {
2022            var input = panel.querySelector('#' + row[0]);
2023            var label = panel.querySelector('#' + row[1]);
2024            if (input) input.value = String(row[2]);
2025            if (label) label.textContent = row[3] ? row[3](row[2]) : String(row[2]);
2026        });
2027
2028        var toggles = [
2029            ['setting-catpaw-clickSquish', 'clickSquish'],
2030            ['setting-catpaw-showBeans', 'showBeans'],
2031            ['setting-catpaw-printsEnabled', 'printsEnabled'],
2032            ['setting-catpaw-outlineShow', 'outlineShow']
2033        ];
2034        toggles.forEach(function (row) {
2035            var el = panel.querySelector('#' + row[0]);
2036            if (el) el.checked = c[row[1]] === true;
2037        });
2038
2039        if (catPawSpeciesSelect) catPawSpeciesSelect.setValue(c.pawSpecies);
2040        if (catPawPresetSelect) catPawPresetSelect.setValue(c.pawPreset || 'classic-white');
2041        if (catPawAnchorSelect) {
2042            var anchorVal = c.anchorPreset === 'center' ? 'bottom-center' : c.anchorPreset;
2043            catPawAnchorSelect.setValue(anchorVal);
2044            if (c.anchorPreset === 'center') {
2045                setCatPawArm({ anchorPreset: 'bottom-center' }, { syncUi: false });
2046            }
2047        }
2048        Object.keys(catPawColorPanels).forEach(function (key) {
2049            if (!catPawColorPanels[key]) return;
2050            if (key === 'spriteFur') {
2051                catPawColorPanels.spriteFur.setValue(c.pawFur);
2052            } else {
2053                catPawColorPanels[key].setValue(c[key]);
2054            }
2055        });
2056        updateCatPawPanelVisibility();
2057        updateCatPawArtModeUI(panel);
2058    }
2059
2060    function mountCatPawSettingsPanel(panel) {
2061        if (!panel) return;
2062        var pawPanel = panel.querySelector('#cat-paw-arm-settings');
2063        if (!pawPanel) return;
2064
2065        bindCatPawCollapsible(panel);
2066
2067        if (pawPanel.dataset.mounted !== '1') {
2068            pawPanel.dataset.mounted = '1';
2069
2070            bindCatPawSlider(panel, 'setting-catpaw-originX', 'setting-catpaw-originX-label', 'originX', function (v) { return v + '%'; });
2071            bindCatPawSlider(panel, 'setting-catpaw-originY', 'setting-catpaw-originY-label', 'originY', function (v) { return v + '%'; });
2072            bindCatPawSlider(panel, 'setting-catpaw-armTaper', 'setting-catpaw-armTaper-label', 'armTaper', function (v) { return v + '%'; });
2073            bindCatPawSlider(panel, 'setting-catpaw-armBend', 'setting-catpaw-armBend-label', 'armBend', function (v) { return v + '%'; });
2074            bindCatPawSlider(panel, 'setting-catpaw-followLag', 'setting-catpaw-followLag-label', 'followLag', function (v) { return v + '%'; });
2075            bindCatPawSlider(panel, 'setting-catpaw-pawSize', 'setting-catpaw-pawSize-label', 'pawSize', function (v) { return v + '%'; });
2076            bindCatPawSlider(panel, 'setting-catpaw-padSize', 'setting-catpaw-padSize-label', 'padSize', function (v) { return v + '%'; });
2077            bindCatPawSlider(panel, 'setting-catpaw-printSize', 'setting-catpaw-printSize-label', 'printSize', function (v) { return v + '%'; });
2078            bindCatPawSlider(panel, 'setting-catpaw-printFade', 'setting-catpaw-printFade-label', 'printFade', function (v) { return v + '%'; });
2079            bindCatPawSlider(panel, 'setting-catpaw-printMax', 'setting-catpaw-printMax-label', 'printMax');
2080            bindCatPawSlider(panel, 'setting-catpaw-outlineThickness', 'setting-catpaw-outlineThickness-label', 'outlineThickness', function (v) { return v + '%'; });
2081
2082            [
2083            ['setting-catpaw-clickSquish', 'clickSquish'],
2084            ['setting-catpaw-showBeans', 'showBeans'],
2085            ['setting-catpaw-printsEnabled', 'printsEnabled'],
2086            ['setting-catpaw-outlineShow', 'outlineShow']
2087        ].forEach(function (row) {
2088            var el = panel.querySelector('#' + row[0]);
2089            if (el) {
2090                el.addEventListener('change', function () {
2091                    var patch = {};
2092                    patch[row[1]] = !!el.checked;
2093                    setCatPawArm(patch);
2094                });
2095            }
2096        });
2097
2098            var presetMount = panel.querySelector('#catpaw-preset-mount');
2099            if (presetMount && window.PupwifeSettingsBubbleSelect) {
2100                catPawPresetSelect = window.PupwifeSettingsBubbleSelect.create(presetMount, {
2101                    idPrefix: 'catpaw-preset',
2102                    ariaLabel: 'Paw style',
2103                    options: getCatPawPresetOptions(),
2104                    value: mergeCatPawArm(currentSettings.catPawArm).pawPreset || 'classic-white',
2105                    onChange: function (val) {
2106                        var patch = { pawPreset: val };
2107                        var presetDef = window.CatPawArmCursor && window.CatPawArmCursor.PAW_PRESETS
2108                            ? window.CatPawArmCursor.PAW_PRESETS[val]
2109                            : null;
2110                        if (presetDef && presetDef.species) patch.pawSpecies = presetDef.species;
2111                        if (presetDef && presetDef.defaultFur) {
2112                            patch.pawFur = Object.assign({}, mergeCatPawArm(currentSettings.catPawArm).pawFur, {
2113                                colorMode: 'solid',
2114                                solidColor: presetDef.defaultFur
2115                            });
2116                            patch.arm = Object.assign({}, patch.pawFur);
2117                        }
2118                        setCatPawArm(patch);
2119                        updateCatPawArtModeUI(panel);
2120                    }
2121                });
2122            }
2123
2124            var speciesMount = panel.querySelector('#catpaw-species-mount');
2125            if (speciesMount && window.PupwifeSettingsBubbleSelect) {
2126                catPawSpeciesSelect = window.PupwifeSettingsBubbleSelect.create(speciesMount, {
2127                    idPrefix: 'catpaw-species',
2128                    ariaLabel: 'Paw species',
2129                    options: CAT_PAW_SPECIES_OPTIONS,
2130                    value: mergeCatPawArm(currentSettings.catPawArm).pawSpecies,
2131                    onChange: function (val) {
2132                        setCatPawArm({
2133                            pawSpecies: val === 'dog' ? 'dog' : 'cat',
2134                            pawPreset: val === 'dog' ? '' : 'classic-white'
2135                        });
2136                        updateCatPawArtModeUI(panel);
2137                    }
2138                });
2139            }
2140
2141            var anchorMount = panel.querySelector('#catpaw-anchor-mount');
2142            if (anchorMount && window.PupwifeSettingsBubbleSelect) {
2143                catPawAnchorSelect = window.PupwifeSettingsBubbleSelect.create(anchorMount, {
2144                    idPrefix: 'catpaw-anchor',
2145                    ariaLabel: 'Anchor preset',
2146                    options: CAT_PAW_ANCHOR_OPTIONS,
2147                    value: mergeCatPawArm(currentSettings.catPawArm).anchorPreset,
2148                    onChange: function (val) {
2149                        if (val === 'custom') {
2150                            setCatPawArm({ anchorPreset: 'custom' });
2151                            return;
2152                        }
2153                        var snap = snapCatPawAnchorPreset(val);
2154                        if (snap) setCatPawArm(snap);
2155                    }
2156                });
2157            }
2158
2159            var colorMounts = [
2160                ['catpaw-arm-color-mount', 'arm'],
2161                ['catpaw-fur-color-mount', 'pawFur'],
2162                ['catpaw-pads-color-mount', 'pawPads'],
2163                ['catpaw-outline-color-mount', 'outline'],
2164                ['catpaw-prints-color-mount', 'prints']
2165            ];
2166            var c0 = mergeCatPawArm(currentSettings.catPawArm);
2167            if (window.PupwifeWoimColorPanel) {
2168                colorMounts.forEach(function (row) {
2169                    var mount = panel.querySelector('#' + row[0]);
2170                    if (!mount) return;
2171                    catPawColorPanels[row[1]] = window.PupwifeWoimColorPanel.mount(mount, {
2172                        idPrefix: 'catpaw-' + row[1] + '-color',
2173                        value: c0[row[1]],
2174                        onChange: (function (colorKey) {
2175                            return function (val) {
2176                                var patch = {};
2177                                patch[colorKey] = val;
2178                                setCatPawArm(patch);
2179                            };
2180                        })(row[1])
2181                    });
2182                });
2183                var spriteFurMount = panel.querySelector('#catpaw-sprite-fur-color-mount');
2184                if (spriteFurMount) {
2185                    catPawColorPanels.spriteFur = window.PupwifeWoimColorPanel.mount(spriteFurMount, {
2186                        idPrefix: 'catpaw-sprite-fur-color',
2187                        value: c0.pawFur,
2188                        onChange: function (val) {
2189                            setCatPawArm({ pawFur: val, arm: val });
2190                        }
2191                    });
2192                }
2193            }
2194
2195            var randColors = panel.querySelector('#catpaw-randomize-colors');
2196            if (randColors) {
2197                randColors.addEventListener('click', function () { randomizeCatPawColors(); });
2198            }
2199            var randShape = panel.querySelector('#catpaw-randomize-shape');
2200            if (randShape) {
2201                randShape.addEventListener('click', function () { randomizeCatPawShape(); });
2202            }
2203            var randAll = panel.querySelector('#catpaw-randomize-all');
2204            if (randAll) {
2205                randAll.addEventListener('click', function () { randomizeCatPawAll(); });
2206            }
2207        }
2208
2209        syncCatPawSettingsUI(panel);
2210    }
2211
2212    var trailColorPickers = { primary: null, secondary: null, tertiary: null };
2213
2214    function getMergedTrailColors() {
2215        if (window.PupwifeTrailColorSupport && typeof window.PupwifeTrailColorSupport.mergeTrailColors === 'function') {
2216            return window.PupwifeTrailColorSupport.mergeTrailColors(currentSettings.trailColors);
2217        }
2218        return currentSettings.trailColors || { primary: '#c8a8ff', secondary: '#ffb3ba', tertiary: '#89cff0' };
2219    }
2220
2221    function setTrailColorsPartial(patch, opts) {
2222        opts = opts || {};
2223        var next = Object.assign({}, getMergedTrailColors(), patch || {});
2224        currentSettings.trailColors = next;
2225        applyTrailColorsSetting(next);
2226        try {
2227            localStorage.setItem('puppywifey_settings', JSON.stringify(currentSettings));
2228        } catch (e) {
2229            emitDevWarn('Failed to save settings:', e);
2230        }
2231        try {
2232            document.dispatchEvent(new CustomEvent('pupwife:setting-changed', {
2233                detail: { key: 'trailColors', value: next }
2234            }));
2235        } catch (e) { /* ignore */ }
2236        if (opts.syncPickers) {
2237            syncTrailColorPickers();
2238        }
2239    }
2240
2241    function updateTrailColorPanelVisibility() {
2242        var wrap = document.getElementById('settings-trail-colors');
2243        if (!wrap) return;
2244        var supports = window.PupwifeTrailColorSupport &&
2245            window.PupwifeTrailColorSupport.supportsTrailColors(currentSettings.cursorTrail);
2246        wrap.hidden = !supports;
2247        wrap.classList.toggle('is-visible', !!supports);
2248    }
2249
2250    function syncTrailColorPickers() {
2251        var colors = getMergedTrailColors();
2252        ['primary', 'secondary', 'tertiary'].forEach(function (key) {
2253            if (trailColorPickers[key] && typeof trailColorPickers[key].setValue === 'function') {
2254                trailColorPickers[key].setValue(colors[key]);
2255            }
2256        });
2257    }
2258
2259    function mountTrailColorPanel(panel) {
2260        if (!panel || !window.PupwifeSettingsColorPicker) return;
2261        var wrap = panel.querySelector('#settings-trail-colors');
2262        if (!wrap || wrap.dataset.mounted === '1') {
2263            updateTrailColorPanelVisibility();
2264            syncTrailColorPickers();
2265            return;
2266        }
2267        wrap.dataset.mounted = '1';
2268        var colors = getMergedTrailColors();
2269        var slots = [
2270            { key: 'primary', label: 'Primary', id: 'trail-color-primary' },
2271            { key: 'secondary', label: 'Secondary', id: 'trail-color-secondary' },
2272            { key: 'tertiary', label: 'Tertiary', id: 'trail-color-tertiary' }
2273        ];
2274        slots.forEach(function (slot) {
2275            var field = document.createElement('div');
2276            field.className = 'settings-trail-color__field';
2277            field.dataset.settingsSearch = slot.label.toLowerCase() + ' trail color';
2278            field.innerHTML = '<span class="settings-field__label">' + slot.label + '</span><div id="' + slot.id + '-mount"></div>';
2279            wrap.appendChild(field);
2280            var mountEl = field.querySelector('#' + slot.id + '-mount');
2281            trailColorPickers[slot.key] = window.PupwifeSettingsColorPicker.mount(mountEl, {
2282                idPrefix: slot.id,
2283                value: colors[slot.key],
2284                onChange: (function (colorKey) {
2285                    return function (hex) {
2286                        var patch = {};
2287                        patch[colorKey] = hex;
2288                        setTrailColorsPartial(patch, { syncPickers: false });
2289                    };
2290                })(slot.key),
2291                onCommit: (function (colorKey) {
2292                    return function (hex) {
2293                        var patch = {};
2294                        patch[colorKey] = hex;
2295                        setTrailColorsPartial(patch, { syncPickers: false });
2296                    };
2297                })(slot.key)
2298            });
2299        });
2300
2301        var matchBtn = panel.querySelector('#settings-trail-match-palette');
2302        if (matchBtn) {
2303            matchBtn.addEventListener('click', function () {
2304                if (!window.PupwifeTrailColorSupport) return;
2305                var matched = window.PupwifeTrailColorSupport.getPaletteTrailColors(currentSettings.palette);
2306                setTrailColorsPartial(matched, { syncPickers: true });
2307            });
2308        }
2309
2310        updateTrailColorPanelVisibility();
2311    }
2312
2313    function randomizeCursorSettings(picks) {
2314        if (picks.trail && window.PupwifeTrailPicker && typeof window.PupwifeTrailPicker.allTrailValues === 'function') {
2315            var trails = window.PupwifeTrailPicker.allTrailValues();
2316            if (trails.length) {
2317                setSetting('cursorTrail', trails[Math.floor(Math.random() * trails.length)]);
2318            }
2319        }
2320        if (picks.trailAmount) {
2321            setSetting('trailAmount', Math.floor(Math.random() * 5));
2322        }
2323        if (picks.trailColors && window.PupwifeTrailColorSupport) {
2324            setTrailColorsPartial(window.PupwifeTrailColorSupport.randomTrailColors(), { syncPickers: true });
2325        }
2326        if (picks.customCursor && window.PupwifeCursorPicker && window.PupwifeCursorPicker.GROUPS) {
2327            if (currentSettings.customCursor === 'cat-paw-arm') {
2328                randomizeCatPawColors();
2329            } else {
2330                var cursorValues = ['none'];
2331                window.PupwifeCursorPicker.GROUPS.forEach(function (group) {
2332                    group.options.forEach(function (opt) {
2333                        cursorValues.push(opt.value);
2334                    });
2335                });
2336                setSetting('customCursor', cursorValues[Math.floor(Math.random() * cursorV
2336alues.length)]);
2337            }
2338        }
2339        if (picks.follower) {
2340            var followers = ['none', 'marsey', 'skeletile', 'orca', 'dragon', 'fish2D', 'woimString', 'jellypuff'];
2341            var nextFollower = followers[Math.floor(Math.random() * followers.length)];
2342            setSetting('follower', nextFollower);
2343            if (nextFollower === 'skeletile' && window.skeletile && typeof window.skeletile.regenerate === 'function') {
2344                window.skeletile.regenerate();
2345            }
2346            if (nextFollower === 'dragon' && window.DragonFollower && typeof window.DragonFollower.regenerate === 'function') {
2347                window.DragonFollower.regenerate();
2348            }
2349        }
2350        updateSettingsUI();
2351    }
2352
2353    function applyImportedSettings(partial) {
2354        Object.keys(partial).forEach(function (key) {
2355            if (key === 'woimString') {
2356                currentSettings.woimString = mergeWoimString(partial.woimString);
2357            } else if (key === 'jellypuff') {
2358                currentSettings.jellypuff = mergeJellypuff(partial.jellypuff);
2359            } else if (key === 'catPawArm') {
2360                currentSettings.catPawArm = mergeCatPawArm(partial.catPawArm);
2361            } else if (key === 'trailColors') {
2362                currentSettings.trailColors = partial.trailColors;
2363            } else if (key === 'trailFavorites') {
2364                currentSettings.trailFavorites = Array.isArray(partial.trailFavorites) ? partial.trailFavorites.slice() : [];
2365            } else {
2366                currentSettings[key] = partial[key];
2367            }
2368        });
2369        saveSettings();
2370        applySettings();
2371        updateSettingsUI();
2372        if (window.PupwifeTrailPicker && typeof window.PupwifeTrailPicker.refreshFavorites === 'function') {
2373            window.PupwifeTrailPicker.refreshFavorites();
2374        }
2375    }
2376
2377    function ensureSettingsHelperScripts() {
2378        return loadScriptOnce('/scripts/effects/settingsDiscoverability.js', 'discoverability')
2379            .then(function () { return loadScriptOnce('/scripts/effects/trailColorSupport.js', 'trail-colors'); })
2380            .then(function () { return loadScriptOnce('/scripts/effects/settingsSearch.js', 'search'); })
2381            .then(function () { return loadScriptOnce('/scripts/effects/settingsExportImport.js', 'export-import'); });
2382    }
2383
2384    function ensureSettingsFontAwesome() {
2385        if (typeof window.ensureSiteFontAwesome === 'function') {
2386            return window.ensureSiteFontAwesome();
2387        }
2388        return new Promise(function (resolve) {
2389            if (document.querySelector('script[src*="ensureSiteFontAwesome.js"]')) {
2390                var pending = document.querySelector('script[src*="ensureSiteFontAwesome.js"]');
2391                function done() {
2392                    if (typeof window.ensureSiteFontAwesome === 'function') {
2393                        window.ensureSiteFontAwesome().then(resolve);
2394                    } else {
2395                        resolve();
2396                    }
2397                }
2398                if (pending.getAttribute('data-fa-loader-ready') === '1') {
2399                    done();
2400                } else {
2401                    pending.addEventListener('load', done, { once: true });
2402                    pending.addEventListener('error', done, { once: true });
2403                }
2404                return;
2405            }
2406            var script = document.createElement('script');
2407            script.src = '/scripts/effects/ensureSiteFontAwesome.js';
2408            script.async = false;
2409            script.setAttribute('data-fa-loader', '1');
2410            script.onload = script.onerror = function () {
2411                script.setAttribute('data-fa-loader-ready', '1');
2412                if (typeof window.ensureSiteFontAwesome === 'function') {
2413                    window.ensureSiteFontAwesome().then(resolve);
2414                } else {
2415                    resolve();
2416                }
2417            };
2418            document.head.appendChild(script);
2419        });
2420    }
2421
2422    function createSettingsUI() {
2423        ensureSettingsAssets();
2424
2425        const backdrop = document.createElement('div');
2426        backdrop.id = 'settings-backdrop';
2427
2428        const panel = document.createElement('div');
2429        panel.id = 'settings-panel';
2430
2431        panel.innerHTML = `
2432            <button type="button" id="settings-close" class="settings-panel__close" aria-label="Close settings">
2433                <i class="fa-solid fa-xmark fa-width-auto" aria-hidden="true"></i>
2434            </button>
2435
2436            <header class="settings-panel__header">
2437                <h1 class="settings-panel__title">
2438                    <i class="fa-solid fa-sliders fa-width-auto" aria-hidden="true"></i> Settings
2439                </h1>
2440                <p class="settings-panel__subtitle">Personalize your experience</p>
2441            </header>
2442
2443            <div id="account-access-card" class="settings-card settings-card--account" aria-live="polite" data-settings-tab="account" data-settings-search="account sign in login guest"></div>
2444
2445            <div class="settings-search" data-settings-search-ignore>
2446                <i class="fa-solid fa-magnifying-glass fa-width-auto settings-search__icon" aria-hidden="true"></i>
2447                <input type="search" id="settings-search-input" class="settings-search__input" placeholder="Search settings…" autocomplete="off" aria-label="Search settings">
2448            </div>
2449            <p id="settings-search-empty" class="settings-search-empty" data-settings-search-ignore hidden>No matching settings. Try another word?</p>
2450
2451            <section class="settings-section settings-section--compact" data-settings-tab="appearance" data-settings-section="theme" data-settings-search="theme appearance palette dark light color background caustics ambient shader">
2452                <h2 class="settings-section__title"><i class="fa-solid fa-palette fa-width-auto" aria-hidden="true"></i> Theme</h2>
2453                <div class="settings-card settings-appearance-card" id="settings-appearance-card">
2454                    <div class="settings-appearance-grid">
2455                        <div class="settings-appearance-item settings-appearance-item--mode" data-settings-search="dark mode light theme">
2456                            <p class="settings-card__label"><i class="fa-solid fa-circle-half-stroke fa-width-auto" aria-hidden="true"></i> Light / dark</p>
2457                            <div class="settings-theme-card settings-theme-card--inline" id="settings-theme-card">
2458                                <div class="settings-theme-card__row">
2459                                    <i class="fa-solid fa-sun fa-width-auto settings-theme-card__icon settings-theme-card__icon--sun" id="theme-icon-sun" aria-hidden="true"></i>
2460                                    <label class="settings-switch" title="Toggle dark mode">
2461                                        <input type="checkbox" id="setting-darkMode" class="settings-switch__input">
2462                                        <span class="settings-switch__track" aria-hidden="true"></span>
2463                                    </label>
2464                                    <i class="fa-solid fa-moon fa-width-auto settings-theme-card__icon settings-theme-card__icon--moon" id="theme-icon-moon" aria-hidden="true"></i>
2465                                </div>
2466                            </div>
2467                        </div>
2468                        <div class="settings-appearance-item settings-appearance-item--palette" data-settings-search="color palette theme swatch">
2469                            <label class="settings-card__label" for="setting-palette-trigger">
2470                                <i class="fa-solid fa-swatchbook fa-width-auto" aria-hidden="true"></i> Color palette
2471                            </label>
2472                            <div id="setting-palette-picker" class="settings-palette-picker-host"></div>
2473                        </div>
2474                        <div class="settings-appearance-item settings-appearance-item--background settings-appearance-grid__full" data-settings-search="background caustics aurora ambient shader water horizon clouds tuning sliders">
2475                            <label class="settings-card__label" for="setting-background-trigger">
2476                                <i class="fa-solid fa-water fa-width-auto" aria-hidden="true"></i> Background
2477                            </label>
2478                            <div id="setting-background-picker" class="settings-background-picker-host"></div>
2479                            <div id="setting-background-clouds-warning" class="settings-alert" style="display: none;" role="status">
2480                                <i class="fa-solid fa-triangle-exclamation fa-width-auto" aria-hidden="true"></i>
2481                                <div>
2482                                    <strong>Clouds needs WebGL2</strong>
2483                                    <span class="settings-alert__sub">Your browser cannot run the Clouds shader, so Caustics is showing instead. Try a newer browser or enable hardware acceleration.</span>
2484                                </div>
2485                            </div>
2486                            <div id="setting-background-tuning" class="settings-woim-panel settings-bg-tuning-panel is-visible is-collapsed">
2487                                <button type="button" id="setting-background-tuning-toggle" class="settings-woim-panel__toggle" aria-expanded="false" aria-controls="setting-background-tuning-body">
2488                                    <span class="settings-woim-panel__toggle-label"><i class="fa-solid fa-sliders fa-width-auto" aria-hidden="true"></i> Background tuning</span>
2489                                    <i class="fa-solid fa-chevron-down settings-woim-panel__chevron fa-width-auto" aria-hidden="true"></i>
2490                                </button>
2491                                <div id="setting-background-tuning-body" class="settings-woim-panel__body"></div>
2492                            </div>
2493                        </div>
2494                        <div class="settings-appearance-item settings-appearance-grid__full settings-citrus-wallpaper-block" id="settings-citrus-wallpaper-section" data-settings-search="citrusos desktop wallpaper dim darken saturation brightness blur overlay filter tweak" hidden>
2495                            <div class="settings-card settings-appearance-card settings-citrus-wallpaper-card">
2496                                <div class="settings-field settings-woim-slider" data-settings-search="wallpaper dim darken overlay desktop">
2497                                    <span class="settings-field__label"><i class="fa-solid fa-circle-half-stroke fa-width-auto" aria-hidden="true"></i> Dim overlay</span>
2498                                    <div class="settings-trail-amount">
2499                                        <input type="range" id="setting-citrusWallpaperDim" class="settings-trail-amount__slider settings-citrus-slider" min="0" max="50" step="1" value="0">
2500                                        <output class="settings-trail-amount__value" id="setting-citrusWallpaperDim-label" for="setting-citrusWallpaperDim">0%</output>
2501                                    </div>
2502                                </div>
2503                                <div class="settings-field settings-woim-slider" data-settings-search="wallpaper saturation color vivid desktop">
2504                                    <span class="settings-field__label"><i class="fa-solid fa-droplet fa-width-auto" aria-hidden="true"></i> Saturation</span>
2505                                    <div class="settings-trail-amount">
2506                                        <input type="range" id="setting-citrusWallpaperSaturation" class="settings-trail-amount__slider settings-citrus-slider" min="50" max="150" step="5" value="100">
2507                                        <output class="settings-trail-amount__value" id="setting-citrusWallpaperSaturation-label" for="setting-citrusWallpaperSaturation">100%</output>
2508                                    </div>
2509                                </div>
2510                                <div class="settings-field settings-woim-slider" data-settings-search="wallpaper brightness lighten darken desktop">
2511                                    <span class="settings-field__label"><i class="fa-solid fa-sun fa-width-auto" aria-hidden="true"></i> Brightness</span>
2512                                    <div class="settings-trail-amount">
2513                                        <input type="range" id="setting-citrusWallpaperBrightness" class="settings-trail-amount__slider settings-citrus-slider" min="50" max="150" step="5" value="100">
2514                                        <output class="settings-trail-amount__value" id="setting-citrusWallpaperBrightness-label" for="setting-citrusWallpaperBrightness">100%</output>
2515                                    </div>
2516                                </div>
2517                                <div class="settings-field settings-woim-slider" data-settings-search="wallpaper blur soften desktop">
2518                                    <span class="settings-field__label"><i class="fa-solid fa-wand-magic-sparkles fa-width-auto" aria-hidden="true"></i> Blur</span>
2519                                    <div class="settings-trail-amount">
2520                                        <input type="range" id="setting-citrusWallpaperBlur" class="settings-trail-amount__slider settings-citrus-slider" min="0" max="12" step="1" value="0">
2521                                        <output class="settings-trail-amount__value" id="setting-citrusWallpaperBlur-label" for="setting-citrusWallpaperBlur">0 px</output>
2522                                    </div>
2523                                </div>
2524                                <div class="settings-inline-actions">
2525                                    <button type="button" id="settings-citrus-wallpaper-reset" class="settings-btn settings-btn--compact settings-desktop-action-btn" data-settings-search="reset wallpaper desktop tweaks defaults">
2526                                        <i class="fa-solid fa-rotate-left fa-width-auto" aria-hidden="true"></i> Reset wallpaper tweaks
2527                                    </button>
2528                                </div>
2529                            </div>
2530                        </div>
2531                    </div>
2532                </div>
2533            </section>
2534
2535            <section class="settings-section settings-section--compact" id="settings-citrusos-section" data-settings-tab="desktop" data-settings-section="general" data-settings-search="citrusos desktop ui scale size taskbar icons start menu general" hidden>
2536                <h2 class="settings-section__title"><i class="fa-solid fa-sliders fa-width-auto" aria-hidden="true"></i> General</h2>
2537                <div class="settings-card">
2538                    <div class="settings-field" data-settings-search="ui scale size desktop icons taskbar readability">
2539                        <span class="settings-field__label"><i class="fa-solid fa-up-right-and-down-left-from-center fa-width-auto" aria-hidden="true"></i> Desktop UI scale</span>
2540                        <div class="settings-radio-grid settings-radio-grid--scale">
2541                            <label class="settings-radio">
2542                                <input type="radio" name="citrusUiScale" value="100">
2543                                <span class="settings-radio__box"><span>100%</span><small>Default</small></span>
2544                            </label>
2545                            <label class="settings-radio">
2546                                <input type="radio" name="citrusUiScale" value="125">
2547                                <span class="settings-radio__box"><span>125%</span><small>Large</small></span>
2548                            </label>
2549                            <label class="settings-radio">
2550                                <input type="radio" name="citrusUiScale" value="150">
2551                                <span class="settings-radio__box"><span>150%</span><small>Extra large</small></span>
2552                            </label>
2553                        </div>
2554                    </div>
2555                </div>
2556            </section>
2557
2558            <section class="settings-section settings-section--compact" id="settings-taskbar-section" data-settings-tab="desktop" data-settings-section="taskbar" data-settings-search="taskbar auto hide clock date seconds show desktop tray" hidden>
2559                <h2 class="settings-section__title"><i class="fa-solid fa-bars fa-width-auto" aria-hidden="true"></i> Taskbar</h2>
2560                <div class="settings-card">
2561                    <label class="settings-row settings-row--toggle settings-row--compact" data-settings-search="auto hide taskbar edge hover reveal">
2562                        <span class="settings-row__label"><i class="fa-solid fa-eye-slash fa-width-auto settings-row__icon" aria-hidden="true"></i><span>Auto-hide taskbar</span></span>
2563                        <span class="settings-switch"><input type="checkbox" id="setting-taskbarAutoHide" class="settings-switch__input"><span class="settings-switch__track" aria-hidden="true"></span></span>
2564                    </label>
2565                    <label class="settings-row settings-row--toggle settings-row--compact" data-settings-search="show desktop button peek wallpaper">
2566                        <span class="settings-row__label"><i class="fa-solid fa-border-none fa-width-auto settings-row__icon" aria-hidden="true"></i><span>Show Desktop button</span></span>
2567                        <span class="settings-switch"><input type="checkbox" id="setting-showDesktopButton" class="settings-switch__input" checked><span class="settings-switch__track" aria-hidden="true"></span></span>
2568                    </label>
2569                    <label class="settings-row settings-row--toggle settings-row--compact" data-settings-search="clock 24 hour military time">
2570                        <span class="settings-row__label"><i class="fa-solid fa-clock fa-width-auto settings-row__icon" aria-hidden="true"></i><span>24-hour clock</span></span>
2571                        <span class="settings-switch"><input type="checkbox" id="setting-taskbarClock24Hour" class="settings-switch__input"><span class="settings-switch__track" aria-hidden="true"></span></span>
2572                    </label>
2573                    <label class="settings-row settings-row--toggle settings-row--compact" data-settings-search="clock seconds tray time">
2574                        <span class="settings-row__label"><i class="fa-solid fa-stopwatch fa-width-auto settings-row__icon" aria-hidden="true"></i><span>Show seconds on clock</span></span>
2575                        <span class="settings-switch"><input type="checkbox" id="setting-taskbarClockShowSeconds" class="settings-switch__input"><span class="settings-switch__track" aria-hidden="true"></span></span>
2576                    </label>
2577                    <label class="settings-row settings-row--toggle settings-row--compact" data-settings-search="date tray calendar">
2578                        <span class="settings-row__label"><i class="fa-solid fa-calendar-day fa-width-auto settings-row__icon" aria-hidden="true"></i><span>Show date in tray</span></span>
2579                        <span class="settings-switch"><input type="checkbox" id="setting-taskbarShowDate" class="settings-switch__input" checked><span class="settings-switch__track" aria-hidden="true"></span></span>
2580                    </label>
2581                </div>
2582            </section>
2583
2584            <section class="settings-section settings-section--compact" id="settings-windows-section" data-settings-tab="desktop" data-settings-section="windows" data-settings-search="windows titlebar maximize double click snap edge" hidden>
2585                <h2 class="settings-section__title"><i class="fa-solid fa-window-maximize fa-width-auto" aria-hidden="true"></i> Windows</h2>
2586                <div class="settings-card">
2587                    <label class="settings-row settings-row--toggle settings-row--compact" data-settings-search="double click titlebar maximize restore">
2588                        <span class="settings-row__label"><i class="fa-solid fa-up-down fa-width-auto settings-row__icon" aria-hidden="true"></i><span>Double-click titlebar to maximize</span></span>
2589                        <span class="settings-switch"><input type="checkbox" id="setting-windowTitlebarDblClickMaximize" class="settings-switch__input" checked><span class="settings-switch__track" aria-hidden="true"></span></span>
2590                    </label>
2591                </div>
2592            </section>
2593
2594            <section class="settings-section settings-section--compact" id="settings-desktop-section" data-settings-tab="desktop" data-settings-section="desktop" data-settings-search="desktop icons wallpaper arrange reset labels hide show saturation dim tint">
2595                <h2 class="settings-section__title"><i class="fa-solid fa-border-all fa-width-auto" aria-hidden="true"></i> Desktop icons</h2>
2596                <div class="settings-card">
2597                    <label class="settings-row settings-row--toggle settings-row--compact" data-settings-search="show desktop icons hide icons">
2598                        <span class="settings-row__label"><i class="fa-solid fa-icons fa-width-auto settings-row__icon" aria-hidden="true"></i><span>Show desktop icons</span></span>
2599                        <span class="settings-switch"><input type="checkbox" id="setting-desktopIconsVisible" class="settings-switch__input" checked><span class="settings-switch__track" aria-hidden="true"></span></span>
2600                    </label>
2601                    <label class="settings-row settings-row--toggle settings-row--compact" data-settings-search="icon labels text names">
2602                        <span class="settings-row__label"><i class="fa-solid fa-font fa-width-auto settings-row__icon" aria-hidden="true"></i><span>Show icon labels</span></span>
2603                        <span class="settings-switch"><input type="checkbox" id="setting-desktopIconLabelsVisible" class="settings-switch__input" checked><span class="settings-switch__track" aria-hidden="true"></span></span>
2604                    </label>
2605                    <div class="settings-field" data-settings-search="desktop icon size scale bigger smaller">
2606                        <span class="settings-field__label"><i class="fa-solid fa-up-right-and-down-left-from-center fa-width-auto" aria-hidden="true"></i> Icon size</span>
2607                        <div class="settings-radio-grid settings-radio-grid--scale">
2608                            <label class="settings-radio">
2609                                <input type="radio" name="desktopIconScale" value="75">
2610                                <span class="settings-radio__box"><span>75%</span><small>Small</small></span>
2611                            </label>
2612                            <label class="settings-radio">
2613                                <input type="radio" name="desktopIconScale" value="100">
2614                                <span class="settings-radio__box"><span>100%</span><small>Default</small></span>
2615                            </label>
2616                            <label class="settings-radio">
2617                                <input type="radio" name="desktopIconScale" value="125">
2618                                <span class="settings-radio__box"><span>125%</span><small>Large</small></span>
2619                            </label>
2620                            <label class="settings-radio">
2621                                <input type="radio" name="desktopIconScale" value="150">
2622                                <span class="settings-radio__box"><span>150%</span><small>Extra large</small></span>
2623                            </label>
2624                        </div>
2625                    </div>
2626                    <div class="settings-desktop-actions">
2627                        <button type="button" id="settings-desktop-auto-arrange" class="settings-btn settings-btn--compact settings-desktop-action-btn" data-settings-search="auto arrange tidy desktop icons reset icon layout">
2628                            <i class="fa-solid fa-grip fa-width-auto" aria-hidden="true"></i> Reset icon layout
2629                        </button>
2630                        <button type="button" id="settings-desktop-reset-folder-layout" class="settings-btn settings-btn--compact settings-desktop-action-btn" data-settings-search="reset folder layout user folders">
2631                            <i class="fa-solid fa-folder-tree fa-width-auto" aria-hidden="true"></i> Reset folder layout
2632                        </button>
2633                        <button type="button" id="settings-desktop-reset-icons" class="settings-btn settings-btn--compact settings-desktop-action-btn" data-settings-search="reset desktop icons default grid factory">
2634                            <i class="fa-solid fa-rotate-left fa-width-auto" aria-hidden="true"></i> Reset desktop to defaults
2635                        </button>
2636                    </div>
2637                </div>
2638            </section>
2639
2640            <section class="settings-section settings-section--compact" data-settings-tab="cursor" data-settings-section="follower" data-settings-search="follower buddy marsey skeletile dragon orca fish woim jellypuff jellyfish">
2641                <h2 class="settings-section__title"><i class="fa-solid fa-arrow-pointer fa-width-auto" aria-hidden="true"></i> Cursor buddy</h2>
2642                <div id="follower-warning" class="settings-alert" style="display: none;" role="status">
2643                    <i class="fa-solid fa-triangle-exclamation fa-width-auto" aria-hidden="true"></i>
2644                    <div>
2645                        <strong>Followers disabled on chat page</strong>
2646                        <span class="settings-alert__sub">Browser security prevents tracking your cursor inside the chatbox iframe. Followers work normally on all other pages!</span>
2647                    </div>
2648                </div>
2649                <div class="settings-radio-grid">
2650                    <label class="settings-radio">
2651                        <input type="radio" name="follower" value="none">
2652                        <span class="settings-radio__box"><i class="fa-solid fa-ban fa-width-auto" aria-hidden="true"></i><span>None</span></span>
2653                    </label>
2654                    <label class="settings-radio">
2655                        <input type="radio" name="follower" value="marsey">
2656                        <span class="settings-radio__box"><i class="fa-solid fa-cat fa-width-auto" aria-hidden="true"></i><span>Marsey cat</span></span>
2657                    </label>
2658                    <label class="settings-radio settings-radio--full">
2659                        <input type="radio" name="follower" value="skeletile">
2660                        <span class="settings-radio__box"><i class="fa-solid fa-skull fa-width-auto" aria-hidden="true"></i><span>Skeletile <small>(R to regenerate)</small></span></span>
2661                    </label>
2662                    <button type="button" id="regenerate-skeletile-btn" class="settings-regen-btn settings-radio--full" title="Generate a new random skeletile">
2663                        <i class="fa-solid fa-arrows-rotate fa-width-auto" aria-hidden="true"></i> Regenerate
2664                    </button>
2665                    <label class="settings-radio settings-radio--full">
2666                        <input type="radio" name="follower" value="dragon">
2667                        <span class="settings-radio__box"><i class="fa-solid fa-dragon fa-width-auto" aria-hidden="true"></i><span>Dragon <small>(R to regenerate)</small></span></span>
2668                    </label>
2669                    <button type="button" id="regenerate-dragon-btn" class="settings-regen-btn settings-radio--full" title="Generate a new random dragon">
2670                        <i class="fa-solid fa-arrows-rotate fa-width-auto" aria-hidden="true"></i> Regenerate
2671                    </button>
2672                    <label class="settings-radio">
2673                        <input type="radio" name="follower" value="orca">
2674                        <span class="settings-radio__box"><i class="fa-solid fa-fish fa-width-auto" aria-hidden="true"></i><span>Orca</span></span>
2675                    </label>
2676                    <label class="settings-radio">
2677                        <input type="radio" name="follower" value="fish2D">
2678                        <span class="settings-radio__box"><i class="fa-solid fa-fish-fins fa-width-auto" aria-hidden="true"></i><span>2D fish</span></span>
2679                    </label>
2680                    <label class="settings-radio settings-radio--full">
2681                        <input type="radio" name="follower" value="woimString">
2682                        <span class="settings-radio__box"><i class="fa-solid fa-worm fa-width-auto" aria-hidden="true"></i><span>Woim On A String</span></span>
2683                    </label>
2684                    <label class="settings-radio settings-radio--full">
2685                        <input type="radio" name="follower" value="jellypuff">
2686                        <span class="settings-radio__box"><i class="fa-solid fa-droplet fa-width-auto" aria-hidden="true"></i><span>Jellypuff</span></span>
2687                    </label>
2688                </div>
2689                <div id="jellypuff-settings" class="settings-woim-panel is-collapsed">
2690                    <button type="button" id="jellypuff-settings-toggle" class="settings-woim-panel__toggle" aria-expanded="false" aria-controls="jellypuff-settings-body">
2691                        <span class="settings-woim-panel__toggle-label"><i class="fa-solid fa-droplet fa-width-auto" aria-hidden="true"></i> Jellypuff options</span>
2692                        <i class="fa-solid fa-chevron-down settings-woim-panel__chevron fa-width-auto" aria-hidden="true"></i>
2693                    </button>
2694                    <div id="jellypuff-settings-body" class="settings-woim-panel__body">
2695                    <h3 class="settings-woim-panel__title"><i class="fa-solid fa-sliders fa-width-auto" aria-hidden="true"></i> Motion &amp; size</h3>
2696                    <div class="settings-field settings-woim-slider">
2697                        <span class="settings-field__label">Bell size</span>
2698                        <div class="settings-trail-amount">
2699                            <input type="range" id="setting-jellypuff-size" class="settings-trail-amount__slider settings-citrus-slider" min="25" max="200" step="5" value="100">
2700                            <output class="settings-trail-amount__value" id="setting-jellypuff-size-label" for="setting-jellypuff-size">100%</output>
2701                        </div>
2702                    </div>
2703                    <div class="settings-field settings-woim-slider">
2704                        <span class="settings-field__label">Pulse rate</span>
2705                        <div class="settings-trail-amount">
2706                            <input type="range" id="setting-jellypuff-pulseRate" class="settings-trail-amount__slider settings-citrus-slider" min="40" max="200" step="5" value="100">
2707                            <output class="settings-trail-amount__value" id="setting-jellypuff-pulseRate-label" for="setting-jellypuff-pulseRate">100%</output>
2708                        </div>
2709                    </div>
2710                    <div class="settings-field settings-woim-slider">
2711                        <span class="settings-field__label">Tentacle density</span>
2712                        <div class="settings-trail-amount">
2713                            <input type="range" id="setting-jellypuff-tentacleDensity" class="settings-trail-amount__slider settings-citrus-slider" min="50" max="200" step="5" value="100">
2714                            <output class="settings-trail-amount__value" id="setting-jellypuff-tentacleDensity-label" for="setting-jellypuff-tentacleDensity">100%</output>
2715                        </div>
2716                    </div>
2717                    <div class="settings-field settings-woim-slider">
2718                        <span class="settings-field__label">Glow strength</span>
2719                        <div class="settings-trail-amount">
2720                            <input type="range" id="setting-jellypuff-glowStrength" class="settings-trail-amount__slider settings-citrus-slider" min="0" max="200" step="5" value="100">
2721                            <output class="settings-trail-amount__value" id="setting-jellypuff-glowStrength-label" for="setting-jellypuff-glowStrength">100%</output>
2722                        </div>
2723                    </div>
2724                    <h3 class="settings-woim-panel__title"><i class="fa-solid fa-palette fa-width-auto" aria-hidden="true"></i> Bell colors</h3>
2725                    <div id="jellypuff-bell-color-mount"></div>
2726                    <h3 class="settings-woim-panel__title"><i class="fa-solid fa-palette fa-width-auto" aria-hidden="true"></i> Tentacle colors</h3>
2727                    <div id="jellypuff-tentacles-color-mount"></div>
2728                    </div>
2729                </div>
2730                <div id="woim-string-settings" class="settings-woim-panel is-collapsed">
2731                    <button type="button" id="woim-string-settings-toggle" class="settings-woim-panel__toggle" aria-expanded="false" aria-controls="woim-string-settings-body">
2732                        <span class="settings-woim-panel__toggle-label"><i class="fa-solid fa-worm fa-width-auto" aria-hidden="true"></i> Woim On A String options</span>
2733                        <i class="fa-solid fa-chevron-down settings-woim-panel__chevron fa-width-auto" aria-hidden="true"></i>
2734                    </button>
2735                    <div id="woim-string-settings-body" class="settings-woim-panel__body">
2736                    <h3 class="settings-woim-panel__title"><i class="fa-solid fa-link fa-width-auto" aria-hidden="true"></i> String &amp; anchor</h3>
2737                    <div class="settings-field settings-woim-slider">
2738                        <span class="settings-field__label">Rope points</span>
2739                        <div class="settings-trail-amount">
2740                            <input type="range" id="setting-woim-ropePoints" class="settings-trail-amount__slider settings-citrus-slider" min="16" max="64" step="1" value="32" list="setting-woim-ropePoints-ticks">
2741                            <datalist id="setting-woim-ropePoints-ticks">
2742                                <option value="16"></option><option value="24"></option><option value="32"></option><option value="48"></option><option value="64"></option>
2743                            </datalist>
2744                            <output class="settings-trail-amount__value" id="setting-woim-ropePoints-label" for="setting-woim-ropePoints">32</output>
2745                        </div>
2746                    </div>
2747                    <div class="settings-field settings-woim-slider">
2748                        <span class="settings-field__label">Rope length</span>
2749                        <div class="settings-trail-amount">
2750                            <input type="range" id="setting-woim-ropeLength" class="settings-trail-amount__slider settings-citrus-slider" min="50" max="200" step="5" value="100">
2751                            <output class="settings-trail-amount__value" id="setting-woim-ropeLength-label" for="setting-woim-ropeLength">100%</output>
2752                        </div>
2753                    </div>
2754                    <div class="settings-field settings-woim-slider">
2755                        <span class="settings-field__label">Rope thickness</span>
2756                        <div class="settings-trail-amount">
2757                            <input type="range" id="setting-woim-ropeThickness" class="settings-trail-amount__slider settings-citrus-slider" min="25" max="300" step="5" value="100">
2758                            <output class="settings-trail-amount__value" id="setting-woim-ropeThickness-label" for="setting-woim-ropeThickness">100%</output>
2759                        </div>
2760                    </div>
2761                    <label class="settings-row settings-row--toggle">
2762                        <span class="settings-row__label"><span>Anchor ring</span></span>
2763                        <span class="settings-switch">
2764                            <input type="checkbox" id="setting-woim-anchorShow" class="settings-switch__input" checked>
2765                            <span class="settings-switch__track" aria-hidden="true"></span>
2766                        </span>
2767                    </label>
2768                    <div class="settings-field settings-woim-slider">
2769                        <span class="settings-field__label">Ring size</span>
2770                        <div class="settings-trail-amount">
2771                            <input type="range" id="setting-woim-anchorSize" class="settings-trail-amount__slider settings-citrus-slider" min="50" max="200" step="5" value="100">
2772                            <output class="settings-trail-amount__value" id="setting-woim-anchorSize-label" for="setting-woim-anchorSize">100%</output>
2773                        </div>
2774                    </div>
2775                    <div class="settings-field settings-woim-slider">
2776                        <span class="settings-field__label">Ring thickness</span>
2777                        <div class="settings-trail-amount">
2778                            <input type="range" id="setting-woim-anchorThickness" class="settings-trail-amount__slider settings-citrus-slider" min="50" max="200" step="5" value="100">
2779                            <output class="settings-trail-amount__value" id="setting-woim-anchorThickness-label" for="setting-woim-anchorThickness">100%</output>
2780                        </div>
2781                    </div>
2782                    <h3 class="settings-woim-panel__title"><i class="fa-solid fa-palette fa-width-auto" aria-hidden="true"></i> Rope colors</h3>
2783                    <div id="woim-rope-color-mount"></div>
2784                    <h3 class="settings-woim-panel__title"><i class="fa-solid fa-worm fa-width-auto" aria-hidden="true"></i> Squirmle</h3>
2785                    <div class="settings-field settings-woim-slider">
2786                        <span class="settings-field__label">Woim points</span>
2787                        <div class="settings-trail-amount">
2788                            <input type="range" id="setting-woim-bodyPoints" class="settings-trail-amount__slider settings-citrus-slider" min="16" max="64" step="1" value="32" list="setting-woim-bodyPoints-ticks">
2789                            <datalist id="setting-woim-bodyPoints-ticks">
2790                                <option value="16"></option><option value="24"></option><option value="32"></option><option value="48"></option><option value="64"></option>
2791                            </datalist>
2792                            <output class="settings-trail-amount__value" id="setting-woim-bodyPoints-label" for="setting-woim-bodyPoints">32</output>
2793                        </div>
2794                    </div>
2795                    <div class="settings-field settings-woim-slider">
2796                        <span class="settings-field__label">Woim size</span>
2797                        <div class="settings-trail-amount">
2798                            <input type="range" id="setting-woim-size" class="settings-trail-amount__slider settings-citrus-slider" min="25" max="400" step="5" value="100">
2799                            <output class="settings-trail-amount__value" id="setting-woim-size-label" for="setting-woim-size">100%</output>
2800                        </div>
2801                    </div>
2802                    <h3 class="settings-woim-panel__title"><i class="fa-solid fa-palette fa-width-auto" aria-hidden="true"></i> Woim colors</h3>
2803                    <div id="woim-body-color-mount"></div>
2804                    </div>
2805                </div>
2806            </section>
2807
2808            <section class="settings-section settings-section--compact" id="settings-cursor-section" data-settings-tab="cursor" data-settings-section="cursor" data-settings-search="cursor trail pointer mouse sparkles">
2809                <h2 class="settings-section__title"><i class="fa-solid fa-sparkles fa-width-auto" aria-hidden="true"></i> Cursor &amp; trails</h2>
2810
2811                <div class="settings-field" data-settings-search="cursor trail type sparkles fairy paw">
2812                    <span class="settings-field__label"><i class="fa-solid fa-route fa-width-auto" aria-hidden="true"></i> Trail type <small>(star to favorite)</small></span>
2813                    <div class="settings-bubble-select" id="setting-cursorTrail-picker"></div>
2814                </div>
2815
2816                <div class="settings-field settings-trail-amount-row" id="setting-trailAmount-row" data-settings-search="trail amount density particles">
2817                    <span class="settings-field__label" id="setting-trailAmount-label-heading">
2818                        <i class="fa-solid fa-sliders fa-width-auto" aria-hidden="true"></i> Trail amount
2819                    </span>
2820                    <div class="settings-trail-amount">
2821                        <input type="range" id="setting-trailAmount" class="settings-trail-amount__slider settings-citrus-slider" min="0" max="4" step="1" value="2" aria-labelledby="setting-trailAmount-label-heading" aria-valuemin="0" aria-valuemax="4" aria-valuetext="Normal" list="setting-trailAmount-ticks">
2822                        <datalist id="setting-trailAmount-ticks">
2823                            <option value="0" label="Min"></option><option value="1" label="Low"></option><option value="2" label="Norm"></option><option value="3" label="High"></option><option value="4" label="Max"></option>
2824                        </datalist>
2825                        <output id="setting-trailAmount-label" class="settings-trail-amount__value" for="setting-trailAmount">Normal</output>
2826                    </div>
2827                </div>
2828
2829                <div id="settings-trail-colors" class="settings-trail-colors" hidden data-settings-search="trail colors primary secondary tertiary palette customize">
2830                    <p class="settings-subsection__title"><i class="fa-solid fa-palette fa-width-auto" aria-hidden="true"></i> Trail colors</p>
2831                </div>
2832
2833                <div class="settings-inline-actions" data-settings-search="match palette randomize cursor dice shuffle">
2834                    <button type="button" id="settings-trail-match-palette" 
2834class="settings-btn settings-btn--compact">
2835                        <i class="fa-solid fa-swatchbook fa-width-auto" aria-hidden="true"></i> Match theme palette
2836                    </button>
2837                    <button type="button" id="settings-randomize-cursor" class="settings-btn settings-btn--compact">
2838                        <i class="fa-solid fa-shuffle fa-width-auto" aria-hidden="true"></i> Randomize cursor stuff
2839                    </button>
2840                </div>
2841
2842                <div class="settings-field settings-field--compact" data-settings-search="custom cursor mouse pointer asriel axolotl recursive stretchy paw cat dog">
2843                    <span class="settings-field__label"><i class="fa-solid fa-computer-mouse fa-width-auto" aria-hidden="true"></i> Custom cursor</span>
2844                    <div class="settings-bubble-select" id="setting-customCursor-picker"></div>
2845                </div>
2846
2847                <div id="cat-paw-arm-settings" class="settings-woim-panel is-collapsed">
2848                    <button type="button" id="cat-paw-arm-settings-toggle" class="settings-woim-panel__toggle" aria-expanded="false" aria-controls="cat-paw-arm-settings-body">
2849                        <span class="settings-woim-panel__toggle-label"><i class="fa-solid fa-paw fa-width-auto" aria-hidden="true"></i> Stretchy paw options</span>
2850                        <i class="fa-solid fa-chevron-down settings-woim-panel__chevron fa-width-auto" aria-hidden="true"></i>
2851                    </button>
2852                    <div id="cat-paw-arm-settings-body" class="settings-woim-panel__body">
2853                    <p class="settings-section__hint catpaw-sprite-art-note" hidden>Fur and arm tint together. Turn off toe beans for the back of the paw.</p>
2854
2855                    <h3 class="settings-woim-panel__title"><i class="fa-solid fa-paw fa-width-auto" aria-hidden="true"></i> Paw</h3>
2856                    <div class="catpaw-settings-grid catpaw-settings-grid--dropdowns">
2857                        <div class="settings-field settings-woim-slider">
2858                            <span class="settings-field__label">Paw style</span>
2859                            <div id="catpaw-preset-mount"></div>
2860                        </div>
2861                        <div class="settings-field settings-woim-slider">
2862                            <span class="settings-field__label">Anchor preset</span>
2863                            <div id="catpaw-anchor-mount"></div>
2864                        </div>
2865                        <div class="settings-field settings-woim-slider catpaw-settings-grid__full catpaw-species-field">
2866                            <span class="settings-field__label">Species</span>
2867                            <div id="catpaw-species-mount"></div>
2868                        </div>
2869                        <div class="settings-field settings-woim-slider">
2870                            <span class="settings-field__label">Paw &amp; arm size</span>
2871                            <div class="settings-trail-amount">
2872                                <input type="range" id="setting-catpaw-pawSize" class="settings-trail-amount__slider settings-citrus-slider" min="25" max="400" step="5" value="100">
2873                                <output class="settings-trail-amount__value" id="setting-catpaw-pawSize-label" for="setting-catpaw-pawSize">100%</output>
2874                            </div>
2875                        </div>
2876                        <div class="settings-field settings-woim-slider catpaw-procedural-only">
2877                            <span class="settings-field__label">Pad size</span>
2878                            <div class="settings-trail-amount">
2879                                <input type="range" id="setting-catpaw-padSize" class="settings-trail-amount__slider settings-citrus-slider" min="50" max="200" step="5" value="100">
2880                                <output class="settings-trail-amount__value" id="setting-catpaw-padSize-label" for="setting-catpaw-padSize">100%</output>
2881                            </div>
2882                        </div>
2883                        <div class="catpaw-settings-toggles catpaw-settings-grid__full">
2884                            <label class="settings-row settings-row--toggle settings-row--compact">
2885                                <span class="settings-row__label"><span>Click squish</span></span>
2886                                <span class="settings-switch">
2887                                    <input type="checkbox" id="setting-catpaw-clickSquish" class="settings-switch__input" checked>
2888                                    <span class="settings-switch__track" aria-hidden="true"></span>
2889                                </span>
2890                            </label>
2891                            <label class="settings-row settings-row--toggle settings-row--compact">
2892                                <span class="settings-row__label"><span>Toe beans</span></span>
2893                                <span class="settings-switch">
2894                                    <input type="checkbox" id="setting-catpaw-showBeans" class="settings-switch__input" checked>
2895                                    <span class="settings-switch__track" aria-hidden="true"></span>
2896                                </span>
2897                            </label>
2898                        </div>
2899                    </div>
2900
2901                    <h3 class="settings-woim-panel__title catpaw-sprite-only" hidden><i class="fa-solid fa-palette fa-width-auto" aria-hidden="true"></i> Fur &amp; arm color</h3>
2902                    <div id="catpaw-sprite-fur-color-mount" class="catpaw-sprite-only catpaw-settings-grid__full" hidden></div>
2903
2904                    <h3 class="settings-woim-panel__title"><i class="fa-solid fa-link fa-width-auto" aria-hidden="true"></i> Arm</h3>
2905                    <div class="catpaw-settings-grid">
2906                        <div class="settings-field settings-woim-slider">
2907                            <span class="settings-field__label">Origin X</span>
2908                            <div class="settings-trail-amount">
2909                                <input type="range" id="setting-catpaw-originX" class="settings-trail-amount__slider settings-citrus-slider" min="-20" max="120" step="1" value="50">
2910                                <output class="settings-trail-amount__value" id="setting-catpaw-originX-label" for="setting-catpaw-originX">50%</output>
2911                            </div>
2912                        </div>
2913                        <div class="settings-field settings-woim-slider">
2914                            <span class="settings-field__label">Origin Y</span>
2915                            <div class="settings-trail-amount">
2916                                <input type="range" id="setting-catpaw-originY" class="settings-trail-amount__slider settings-citrus-slider" min="-20" max="120" step="1" value="108">
2917                                <output class="settings-trail-amount__value" id="setting-catpaw-originY-label" for="setting-catpaw-originY">108%</output>
2918                            </div>
2919                        </div>
2920                        <div class="settings-field settings-woim-slider">
2921                            <span class="settings-field__label">Arm taper</span>
2922                            <div class="settings-trail-amount">
2923                                <input type="range" id="setting-catpaw-armTaper" class="settings-trail-amount__slider settings-citrus-slider" min="0" max="100" step="5" value="35">
2924                                <output class="settings-trail-amount__value" id="setting-catpaw-armTaper-label" for="setting-catpaw-armTaper">35%</output>
2925                            </div>
2926                        </div>
2927                        <div class="settings-field settings-woim-slider">
2928                            <span class="settings-field__label">Arm bend amount</span>
2929                            <div class="settings-trail-amount">
2930                                <input type="range" id="setting-catpaw-armBend" class="settings-trail-amount__slider settings-citrus-slider" min="0" max="200" step="5" value="0">
2931                                <output class="settings-trail-amount__value" id="setting-catpaw-armBend-label" for="setting-catpaw-armBend">0%</output>
2932                            </div>
2933                        </div>
2934                    </div>
2935
2936                    <h3 class="settings-woim-panel__title"><i class="fa-solid fa-arrows-up-down-left-right fa-width-auto" aria-hidden="true"></i> Motion</h3>
2937                    <div class="catpaw-settings-grid">
2938                        <div class="settings-field settings-woim-slider catpaw-settings-grid__full">
2939                            <span class="settings-field__label">Follow lag</span>
2940                            <div class="settings-trail-amount">
2941                                <input type="range" id="setting-catpaw-followLag" class="settings-trail-amount__slider settings-citrus-slider" min="5" max="100" step="1" value="42">
2942                                <output class="settings-trail-amount__value" id="setting-catpaw-followLag-label" for="setting-catpaw-followLag">42%</output>
2943                            </div>
2944                        </div>
2945                    </div>
2946
2947                    <h3 class="settings-woim-panel__title"><i class="fa-solid fa-shoe-prints fa-width-auto" aria-hidden="true"></i> Paw prints</h3>
2948                    <div class="catpaw-settings-grid">
2949                        <div class="catpaw-settings-toggles catpaw-settings-grid__full">
2950                            <label class="settings-row settings-row--toggle settings-row--compact">
2951                                <span class="settings-row__label"><span>Paw prints on click</span></span>
2952                                <span class="settings-switch">
2953                                    <input type="checkbox" id="setting-catpaw-printsEnabled" class="settings-switch__input" checked>
2954                                    <span class="settings-switch__track" aria-hidden="true"></span>
2955                                </span>
2956                            </label>
2957                        </div>
2958                        <div class="settings-field settings-woim-slider">
2959                            <span class="settings-field__label">Print size</span>
2960                            <div class="settings-trail-amount">
2961                                <input type="range" id="setting-catpaw-printSize" class="settings-trail-amount__slider settings-citrus-slider" min="25" max="200" step="5" value="100">
2962                                <output class="settings-trail-amount__value" id="setting-catpaw-printSize-label" for="setting-catpaw-printSize">100%</output>
2963                            </div>
2964                        </div>
2965                        <div class="settings-field settings-woim-slider">
2966                            <span class="settings-field__label">Print fade</span>
2967                            <div class="settings-trail-amount">
2968                                <input type="range" id="setting-catpaw-printFade" class="settings-trail-amount__slider settings-citrus-slider" min="25" max="200" step="5" value="100">
2969                                <output class="settings-trail-amount__value" id="setting-catpaw-printFade-label" for="setting-catpaw-printFade">100%</output>
2970                            </div>
2971                        </div>
2972                        <div class="settings-field settings-woim-slider catpaw-settings-grid__full">
2973                            <span class="settings-field__label">Max prints</span>
2974                            <div class="settings-trail-amount">
2975                                <input type="range" id="setting-catpaw-printMax" class="settings-trail-amount__slider settings-citrus-slider" min="4" max="32" step="1" value="16">
2976                                <output class="settings-trail-amount__value" id="setting-catpaw-printMax-label" for="setting-catpaw-printMax">16</output>
2977                            </div>
2978                        </div>
2979                    </div>
2980
2981                    <h3 class="settings-woim-panel__title catpaw-procedural-only"><i class="fa-solid fa-palette fa-width-auto" aria-hidden="true"></i> Colors</h3>
2982                    <div class="catpaw-settings-grid catpaw-procedural-only">
2983                        <div class="catpaw-settings-grid__full">
2984                            <span class="settings-field__label">Arm color</span>
2985                            <div id="catpaw-arm-color-mount"></div>
2986                        </div>
2987                        <div class="catpaw-settings-grid__full">
2988                            <span class="settings-field__label">Paw fur</span>
2989                            <div id="catpaw-fur-color-mount"></div>
2990                        </div>
2991                        <div class="catpaw-settings-grid__full">
2992                            <span class="settings-field__label">Paw pads</span>
2993                            <div id="catpaw-pads-color-mount"></div>
2994                        </div>
2995                        <div class="catpaw-settings-grid__full">
2996                            <span class="settings-field__label">Outline</span>
2997                            <label class="settings-row settings-row--toggle settings-row--compact">
2998                                <span class="settings-row__label"><span>Show outline</span></span>
2999                                <span class="settings-switch">
3000                                    <input type="checkbox" id="setting-catpaw-outlineShow" class="settings-switch__input" checked>
3001                                    <span class="settings-switch__track" aria-hidden="true"></span>
3002                                </span>
3003                            </label>
3004                            <div class="settings-field settings-woim-slider">
3005                                <span class="settings-field__label">Outline thickness</span>
3006                                <div class="settings-trail-amount">
3007                                    <input type="range" id="setting-catpaw-outlineThickness" class="settings-trail-amount__slider settings-citrus-slider" min="50" max="200" step="5" value="100">
3008                                    <output class="settings-trail-amount__value" id="setting-catpaw-outlineThickness-label" for="setting-catpaw-outlineThickness">100%</output>
3009                                </div>
3010                            </div>
3011                            <div id="catpaw-outline-color-mount"></div>
3012                        </div>
3013                        <div class="catpaw-settings-grid__full">
3014                            <span class="settings-field__label">Print color</span>
3015                            <div id="catpaw-prints-color-mount"></div>
3016                        </div>
3017                    </div>
3018
3019                    <div class="settings-inline-actions">
3020                        <button type="button" id="catpaw-randomize-colors" class="settings-btn settings-btn--compact catpaw-procedural-only">
3021                            <i class="fa-solid fa-palette fa-width-auto" aria-hidden="true"></i> Randomize colors
3022                        </button>
3023                        <button type="button" id="catpaw-randomize-shape" class="settings-btn settings-btn--compact">
3024                            <i class="fa-solid fa-sliders fa-width-auto" aria-hidden="true"></i> Randomize shape &amp; motion
3025                        </button>
3026                        <button type="button" id="catpaw-randomize-all" class="settings-btn settings-btn--compact">
3027                            <i class="fa-solid fa-shuffle fa-width-auto" aria-hidden="true"></i> Randomize all paw options
3028                        </button>
3029                    </div>
3030                    </div>
3031                </div>
3032            </section>
3033
3034            <section class="settings-section settings-section--compact" data-settings-tab="effects" data-settings-section="effects" data-settings-search="performance effects overlay sounds sparks weather seasonal home landing">
3035                <h2 class="settings-section__title"><i class="fa-solid fa-wand-magic-sparkles fa-width-auto" aria-hidden="true"></i> Effects &amp; performance</h2>
3036
3037                <label class="settings-row settings-row--toggle settings-row--compact" data-settings-search="performance mode fps lag slow">
3038                    <span class="settings-row__label">
3039                        <i class="fa-solid fa-bolt fa-width-auto settings-row__icon" aria-hidden="true"></i>
3040                        <span>Performance mode <small>(disables optional effects)</small></span>
3041                    </span>
3042                    <span class="settings-switch">
3043                        <input type="checkbox" id="setting-performanceMode" class="settings-switch__input">
3044                        <span class="settings-switch__track" aria-hidden="true"></span>
3045                    </span>
3046                </label>
3047
3048                <div id="visual-effects-section" class="settings-subsection">
3049                    <p class="settings-section__hint">Optional overlays — off when performance mode is on</p>
3050
3051                    <label class="settings-row settings-row--toggle settings-row--compact" data-settings-search="ui sounds clicks whoosh audio">
3052                        <span class="settings-row__label"><i class="fa-solid fa-volume-high fa-width-auto settings-row__icon" aria-hidden="true"></i><span>UI sounds</span></span>
3053                        <span class="settings-switch"><input type="checkbox" id="setting-uiSounds" class="settings-switch__input"><span class="settings-switch__track" aria-hidden="true"></span></span>
3054                    </label>
3055                    <label class="settings-row settings-row--toggle settings-row--compact" data-settings-search="click sparks burst particles">
3056                        <span class="settings-row__label"><i class="fa-solid fa-burst fa-width-auto settings-row__icon" aria-hidden="true"></i><span>Click sparks</span></span>
3057                        <span class="settings-switch"><input type="checkbox" id="setting-clickSparks" class="settings-switch__input"><span class="settings-switch__track" aria-hidden="true"></span></span>
3058                    </label>
3059                    <label class="settings-row settings-row--toggle settings-row--compact" data-settings-search="seasonal holiday halloween christmas overlay">
3060                        <span class="settings-row__label"><i class="fa-solid fa-calendar-days fa-width-auto settings-row__icon" aria-hidden="true"></i><span>Seasonal overlay</span></span>
3061                        <span class="settings-switch"><input type="checkbox" id="setting-seasonalOverlay" class="settings-switch__input"><span class="settings-switch__track" aria-hidden="true"></span></span>
3062                    </label>
3063                    <label class="settings-row settings-row--toggle settings-row--compact" data-settings-search="weather rain snow overlay">
3064                        <span class="settings-row__label"><i class="fa-solid fa-cloud-sun-rain fa-width-auto settings-row__icon" aria-hidden="true"></i><span>Weather overlay</span></span>
3065                        <span class="settings-switch"><input type="checkbox" id="setting-weatherOverlay" class="settings-switch__input"><span class="settings-switch__track" aria-hidden="true"></span></span>
3066                    </label>
3067                    <label class="settings-row settings-row--toggle settings-row--compact" data-settings-search="home landing animation index homepage fluid">
3068                        <span class="settings-row__label"><i class="fa-solid fa-water fa-width-auto settings-row__icon" aria-hidden="true"></i><span>Home landing animation <small>(index)</small></span></span>
3069                        <span class="settings-switch"><input type="checkbox" id="setting-homeLanding" class="settings-switch__input"><span class="settings-switch__track" aria-hidden="true"></span></span>
3070                    </label>
3071                </div>
3072            </section>
3073
3074            <footer class="settings-footer" data-settings-tab="advanced" data-settings-search="reset export import tour logout backup share music preload boot" data-settings-search-ignore>
3075                <div class="settings-card settings-card--compact" data-settings-search="music preload boot cache metadata songs library faster load">
3076                    <span class="settings-field__label"><i class="fa-solid fa-music fa-width-auto" aria-hidden="true"></i> Music library preload</span>
3077                    <p class="settings-section__hint">Controls how much music data loads during CitrusOS boot. Turn down for faster startup if you rarely use Music.</p>
3078                    <div class="settings-radio-grid settings-radio-grid--scale">
3079                        <label class="settings-radio">
3080                            <input type="radio" name="musicPreload" value="off">
3081                            <span class="settings-radio__box"><span>Off</span><small>Fastest boot</small></span>
3082                        </label>
3083                        <label class="settings-radio">
3084                            <input type="radio" name="musicPreload" value="metadata">
3085                            <span class="settings-radio__box"><span>Metadata</span><small>Durations only</small></span>
3086                        </label>
3087                        <label class="settings-radio">
3088                            <input type="radio" name="musicPreload" value="full">
3089                            <span class="settings-radio__box"><span>Full</span><small>Cache songs</small></span>
3090                        </label>
3091                    </div>
3092                </div>
3093                <div class="settings-footer__actions">
3094                    <button type="button" id="settings-export" class="settings-btn">
3095                        <i class="fa-solid fa-file-export fa-width-auto" aria-hidden="true"></i> Export
3096                    </button>
3097                    <button type="button" id="settings-import" class="settings-btn">
3098                        <i class="fa-solid fa-file-import fa-width-auto" aria-hidden="true"></i> Import
3099                    </button>
3100                    <button type="button" id="settings-reset" class="settings-btn">
3101                        <i class="fa-solid fa-rotate-left fa-width-auto" aria-hidden="true"></i> Reset defaults
3102                    </button>
3103                    <button type="button" id="settings-replay-home-tour" class="settings-btn">
3104                        <i class="fa-solid fa-route fa-width-auto" aria-hidden="true"></i> Replay site tour
3105                    </button>
3106                    <button type="button" id="settings-logout" class="settings-btn settings-btn--danger">
3107                        <i class="fa-solid fa-right-from-bracket fa-width-auto" aria-hidden="true"></i> Log out
3108                    </button>
3109                </div>
3110                <p class="settings-footer__note">Settings are saved locally in your browser~ :3c</p>
3111            </footer>
3112        `;
3113
3114        // Append to page
3115        if (isCitrusOsPage()) {
3116            panel.id = 'settings-panel';
3117            panel.style.display = 'none';
3118            document.body.appendChild(panel);
3119        } else {
3120            document.body.appendChild(backdrop);
3121            document.body.appendChild(panel);
3122        }
3123
3124        // Check if we're on chat page and show warning
3125        const isOnChatPage = window.location.pathname.includes('chat.html') || 
3126                            window.location.pathname.endsWith('chat');
3127        
3128        if (isOnChatPage) {
3129            // Show warning
3130            const warning = panel.querySelector('#follower-warning');
3131            if (warning) {
3132                warning.style.display = 'block';
3133            }
3134            
3135            // Disable follower options (except "none") and force "none" selection
3136            panel.querySelectorAll('input[name="follower"]').forEach(radio => {
3137                var label = radio.closest('.settings-radio');
3138                if (radio.value !== 'none') {
3139                    radio.disabled = true;
3140                    if (label) label.classList.add('is-disabled');
3141                } else {
3142                    radio.checked = true;
3143                }
3144            });
3145            
3146            emitDevLog('⚠️ Settings menu: Chat page detected, followers disabled');
3147        } else {
3148            const warning = panel.querySelector('#follower-warning');
3149            if (warning) {
3150                warning.style.display = 'none';
3151            }
3152            
3153            panel.querySelectorAll('input[name="follower"]').forEach(radio => {
3154                radio.disabled = false;
3155                var label = radio.closest('.settings-radio');
3156                if (label) label.classList.remove('is-disabled');
3157            });
3158        }
3159
3160        // Event listeners
3161        if (backdrop.parentNode) {
3162            backdrop.addEventListener('click', closeSettings);
3163        }
3164        var closeBtn = panel.querySelector('#settings-close');
3165        if (closeBtn) {
3166            closeBtn.addEventListener('click', closeSettings);
3167        }
3168        panel.querySelector('#settings-reset').addEventListener('click', resetSettings);
3169
3170        var replayTourBtn = panel.querySelector('#settings-replay-home-tour');
3171        if (replayTourBtn) {
3172            replayTourBtn.addEventListener('click', function () {
3173                if (window.HomeTour && typeof window.HomeTour.replay === 'function') {
3174                    window.HomeTour.replay();
3175                } else if (typeof window.replayHomeTour === 'function') {
3176                    window.replayHomeTour();
3177                } else {
3178                    try {
3179                        sessionStorage.setItem('pupwife_replayHomeTour', '1');
3180                    } catch (e) {}
3181                    window.location.href = 'index.html';
3182                }
3183            });
3184        }
3185
3186        // Setting change listeners (skip nested woim / stretchy-paw panel toggles — they have their own handlers)
3187        panel.querySelectorAll('input[type="checkbox"]').forEach(checkbox => {
3188            var id = checkbox.id || '';
3189            if (id.indexOf('setting-catpaw-') === 0 || id.indexOf('setting-woim-') === 0 || id.indexOf('setting-jellypuff-') === 0) return;
3190            if (checkbox.id === 'setting-darkMode') {
3191                checkbox.addEventListener('change', handleDarkModeToggle);
3192            } else {
3193                checkbox.addEventListener('change', handleSettingChange);
3194            }
3195        });
3196
3197        panel.querySelectorAll('input[name="follower"]').forEach(radio => {
3198            radio.addEventListener('change', handleFollowerChange);
3199        });
3200
3201        panel.querySelectorAll('input[name="citrusUiScale"]').forEach(function (radio) {
3202            radio.addEventListener('change', handleCitrusUiScaleChange);
3203        });
3204
3205        panel.querySelectorAll('input[name="desktopIconScale"]').forEach(function (radio) {
3206            radio.addEventListener('change', handleDesktopIconScaleChange);
3207        });
3208
3209        panel.querySelectorAll('input[name="musicPreload"]').forEach(function (radio) {
3210            radio.addEventListener('change', handleMusicPreloadChange);
3211        });
3212
3213        bindDesktopWallpaperSliders(panel);
3214        bindDesktopIconActionButtons(panel);
3215
3216        syncCitrusSettingsSectionVisibility();
3217        syncSettingsPanelCitrusTheme();
3218
3219        mountCursorTrailPicker(panel);
3220        mountCustomCursorPicker(panel);
3221        mountPalettePicker(panel);
3222        mountBackgroundPicker(panel);
3223        mountBackgroundTuning(panel);
3224        updateCloudsBackgroundWarning();
3225        document.addEventListener('pupwife:clouds-fallback-changed', updateCloudsBackgroundWarning);
3226        mountWoimSettingsPanel(panel);
3227        ensureWoimColorPanelScript().then(function () {
3228            mountJellypuffSettingsPanel(panel);
3229            mountCatPawSettingsPanel(panel);
3230        });
3231        mountTrailColorPanel(panel);
3232
3233        if (window.PupwifeSettingsSearch && typeof window.PupwifeSettingsSearch.bind === 'function') {
3234            window.PupwifeSettingsSearch.bind(panel);
3235        }
3236
3237        var randomizeBtn = panel.querySelector('#settings-randomize-cursor');
3238        if (randomizeBtn) {
3239            randomizeBtn.addEventListener('click', function () {
3240                if (window.PupwifeSettingsDiscoverability) {
3241                    window.PupwifeSettingsDiscoverability.markSeen('settings', 'randomize-cursor');
3242                }
3243                if (window.PupwifeSettingsExportImport && typeof window.PupwifeSettingsExportImport.openRandomizeDialog === 'function') {
3244                    window.PupwifeSettingsExportImport.openRandomizeDialog(randomizeCursorSettings);
3245                }
3246            });
3247        }
3248
3249        var exportBtn = panel.querySelector('#settings-export');
3250        if (exportBtn) {
3251            exportBtn.addEventListener('click', function () {
3252                if (window.PupwifeSettingsDiscoverability) {
3253                    window.PupwifeSettingsDiscoverability.markSeen('settings', 'export-import');
3254                }
3255                if (window.PupwifeSettingsExportImport && typeof window.PupwifeSettingsExportImport.openExportDialog === 'function') {
3256                    window.PupwifeSettingsExportImport.openExportDialog(currentSettings);
3257                }
3258            });
3259        }
3260
3261        var importBtn = panel.querySelector('#settings-import');
3262        if (importBtn) {
3263            importBtn.addEventListener('click', function () {
3264                if (window.PupwifeSettingsDiscoverability) {
3265                    window.PupwifeSettingsDiscoverability.markSeen('settings', 'export-import');
3266                }
3267                if (window.PupwifeSettingsExportImport && typeof window.PupwifeSettingsExportImport.openImportDialog === 'function') {
3268                    window.PupwifeSettingsExportImport.openImportDialog(applyImportedSettings);
3269                }
3270            });
3271        }
3272
3273        applyNewFeatureBadges(panel);
3274
3275        const trailAmountSlider = panel.querySelector('#setting-trailAmount');
3276        if (trailAmountSlider) {
3277            trailAmountSlider.addEventListener('input', (e) => {
3278                updateTrailAmountLabel(panel);
3279            });
3280            trailAmountSlider.addEventListener('change', (e) => {
3281                const amount = parseTrailAmount(e.target.value);
3282                e.target.value = String(amount);
3283                setSetting('trailAmount', amount);
3284                updateTrailAmountLabel(panel);
3285            });
3286        }
3287
3288        // Weather overlay checkbox
3289        const weatherOverlayCheckbox = panel.querySelector('#setting-weatherOverlay');
3290        if (weatherOverlayCheckbox) {
3291            weatherOverlayCheckbox.addEventListener('change', (e) => {
3292                setSetting('weatherOverlay', e.target.checked);
3293                // Toggle is handled by applySettings()
3294            });
3295        }
3296
3297        // Skeletile regeneration button
3298        const regenerateBtn = panel.querySelector('#regenerate-skeletile-btn');
3299        if (regenerateBtn) {
3300            regenerateBtn.addEventListener('click', () => {
3301                if (window.skeletile && typeof window.skeletile.regenerate === 'function') {
3302                    window.skeletile.regenerate();
3303                    emitDevLog('🦴 Skeletile regenerated!');
3304                } else {
3305                    emitDevWarn('Skeletile regenerate function not available');
3306                }
3307            });
3308            
3309        }
3310        
3311        // Dragon regeneration button
3312        const regenerateDragonBtn = panel.querySelector('#regenerate-dragon-btn');
3313        if (regenerateDragonBtn) {
3314            regenerateDragonBtn.addEventListener('click', () => {
3315                if (window.DragonFollower && typeof window.DragonFollower.regenerate === 'function') {
3316                    window.DragonFollower.regenerate();
3317                    emitDevLog('🐉 Dragon regenerated!');
3318                    // Ensure dragon is shown after regeneration
3319                    // Check if follower switcher is available and dragon is selected
3320                    if (currentSettings.follower === 'dragon') {
3321                        setTimeout(() => {
3322                            // Trigger showDragon through followerSwitcher if available
3323                            if (window.switchFollower) {
3324                                // Just ensure it's visible - the regenerate already handles initialization
3325                                const dragonContainer = document.getElementById('dragon-follower');
3326                                if (dragonContainer) {
3327                                    dragonContainer.style.display = '';
3328                                    dragonContainer.style.opacity = '1';
3329                                }
3330                            }
3331                        }, 200);
3332                    }
3333                } else {
3334                    emitDevWarn('Dragon regenerate function not available');
3335                }
3336            });
3337            
3338        }
3339
3340        // Close with Escape key
3341        document.addEventListener('keydown', (e) => {
3342            if (e.key !== 'Escape') return;
3343            if (isCitrusOsPage() && window.CitrusSettingsWindow && window.CitrusSettingsWindow.isOpen()) {
3344                if (window.PupwifeSettingsColorPicker &&
3345                    typeof window.PupwifeSettingsColorPicker.isOpen === 'function' &&
3346                    window.PupwifeSettingsColorPicker.isOpen()) {
3347                    window.PupwifeSettingsColorPicker.closeOpen();
3348                    return;
3349                }
3350                closeSettings();
3351                return;
3352            }
3353            const panel = document.getElementById('settings-panel');
3354            if (panel && panel.classList.contains('settings-panel--open')) {
3355                if (window.PupwifeSettingsColorPicker &&
3356                    typeof window.PupwifeSettingsColorPicker.isOpen === 'function' &&
3357                    window.PupwifeSettingsColorPicker.isOpen()) {
3358                    window.PupwifeSettingsColorPicker.closeOpen();
3359                    return;
3360                }
3361                closeSettings();
3362            }
3363        });
3364        
3365        // Regenerate Skeletile with R key when Skeletile is selected
3366        document.addEventListener('keydown', (e) => {
3367            // Only trigger if R is pressed and not typing in an input/textarea
3368            if (e.key === 'r' || e.key === 'R') {
3369                const activeElement = document.activeElement;
3370                const isInputFocused = activeElement && (
3371                    activeElement.tagName === 'INPUT' || 
3372                    activeElement.tagName === 'TEXTAREA' ||
3373                    activeElement.isContentEditable
3374                );
3375                
3376                // Only regenerate if not typing in an input field
3377                if (!isInputFocused) {
3378                    if (currentSettings.follower === 'skeletile') {
3379                        if (window.skeletile && typeof window.skeletile.regenerate === 'function') {
3380                            window.skeletile.regenerate();
3381                            emitDevLog('🦴 Skeletile regenerated via R key!');
3382                        }
3383                    } else if (currentSettings.follower === 'dragon') {
3384                        if (window.DragonFollower && typeof window.DragonFollower.regenerate === 'function') {
3385                            window.DragonFollower.regenerate();
3386                            emitDevLog('🐉 Dragon regenerated via R key!');
3387                            // Ensure dragon is shown after regeneration
3388                            setTimeout(() => {
3389                                const dragonContainer = document.getElementById('dragon-follower');
3390                                if (dragonContainer) {
3391                                    dragonContainer.style.display = '';
3392                                    dragonContainer.style.opacity = '1';
3393                                }
3394                            }, 200);
3395                        }
3396                    }
3397                }
3398            }
3399        });
3400
3401        // Logout button
3402        const logoutBtn = panel.querySelector('#settings-logout');
3403        if (logoutBtn) {
3404            logoutBtn.addEventListener('click', () => {
3405                if (confirm('Are you sure you want to log out? You will need to enter your password to log back in.')) {
3406                    if (window.__sharedUsername && typeof window.__sharedUsername.logout === 'function') {
3407                        window.__sharedUsername.logout();
3408                    } else {
3409                        // Fallback if API not available
3410                        localStorage.removeItem('puppy_shared_username');
3411                        localStorage.removeItem('puppy_username_password');
3412                        location.reload();
3413                    }
3414                }
3415            });
3416        }
3417
3418        syncSettingsPanelChatroomTheme();
3419        updateAccountIndicator();
3420        applyChatroomSiteSettingsNotice();
3421        emitDevLog('Settings menu initialized!');
3422    }
3423
3424    function isChatroomPage() {
3425        return /chatroom/.test(window.location.pathname || '');
3426    }
3427
3428    function isChatroomDarkTheme() {
3429        if (window.ChatroomSettings && typeof window.ChatroomSettings.getColorMode === 'function') {
3430            return window.ChatroomSettings.getColorMode() === 'dark';
3431        }
3432        return document.documentElement.getAttribute('data-color-mode') !== 'light';
3433    }
3434
3435    function applyChatroomSiteSettingsNotice() {
3436        const panel = document.getElementById('settings-panel');
3437        if (!panel) return;
3438
3439        const followerSection = panel.querySelector('[data-settings-section="follower"]');
3440        const visualSection = panel.querySelector('#visual-effects-section');
3441        const trailSection = panel.querySelector('#settings-cursor-section');
3442        const perfSection = panel.querySelector('[data-settings-section="effects"]');
3443
3444        let notice = panel.querySelector('#settings-chatroom-effects-notice');
3445        if (isChatroomPage()) {
3446            if (!notice) {
3447                notice = document.createElement('div');
3448                notice.id = 'settings-chatroom-effects-notice';
3449                notice.className = 'settings-alert';
3450                notice.setAttribute('role', 'status');
3451                notice.innerHTML =
3452                    '<i class="fa-solid fa-circle-info fa-width-auto" aria-hidden="true"></i>' +
3453                    '<span>Homepage effects aren\'t available on this page.</span>';
3454                const appearanceCard = panel.querySelector('#settings-appearance-card');
3455                if (appearanceCard) {
3456                    appearanceCard.insertAdjacentElement('afterend', notice);
3457                } else {
3458                    panel.querySelector('.settings-panel__header')?.after(notice);
3459                }
3460            }
3461            notice.style.display = '';
3462            [followerSection, visualSection, trailSection, perfSection].forEac
3462h(function (section) {
3463                if (!section) return;
3464                section.classList.add('settings-section--chatroom-unavailable');
3465                section.querySelectorAll('input, select, button').forEach(function (el) {
3466                    el.disabled = true;
3467                });
3468            });
3469            const subtitle = panel.querySelector('.settings-panel__subtitle');
3470            if (subtitle) {
3471                subtitle.textContent = 'Light/dark for chatroom · effects apply on other pages';
3472            }
3473        } else {
3474            if (notice) notice.style.display = 'none';
3475            [followerSection, visualSection, trailSection, perfSection].forEach(function (section) {
3476                if (!section) return;
3477                section.classList.remove('settings-section--chatroom-unavailable');
3478                section.querySelectorAll('input, select, button').forEach(function (el) {
3479                    el.disabled = false;
3480                });
3481            });
3482        }
3483    }
3484
3485    function syncSettingsPanelChatroomTheme() {
3486        const panel = document.getElementById('settings-panel');
3487        if (!panel) return;
3488        if (isChatroomPage()) {
3489            panel.classList.add('settings-panel--chatroom');
3490            updateDarkModeUI(isChatroomDarkTheme());
3491        } else {
3492            panel.classList.remove('settings-panel--chatroom');
3493        }
3494    }
3495
3496    function syncSettingsPanelCitrusTheme() {
3497        const panel = document.getElementById('settings-panel');
3498        if (!panel) return;
3499        panel.classList.toggle('settings-panel--citrusos', isCitrusOsPage());
3500        syncCitrusSettingsSectionVisibility();
3501        const subtitle = panel.querySelector('.settings-panel__subtitle');
3502        if (subtitle && !isChatroomPage()) {
3503            subtitle.textContent = isCitrusOsPage()
3504                ? 'CitrusOS system preferences'
3505                : 'Personalize your experience';
3506        }
3507    }
3508
3509    function syncCitrusSettingsSectionVisibility() {
3510        const citrusSection = document.getElementById('settings-citrusos-section');
3511        const taskbarSection = document.getElementById('settings-taskbar-section');
3512        const windowsSection = document.getElementById('settings-windows-section');
3513        const desktopSection = document.getElementById('settings-desktop-section');
3514        const wallpaperSection = document.getElementById('settings-citrus-wallpaper-section');
3515        const onCitrus = isCitrusOsPage();
3516        if (citrusSection) citrusSection.hidden = !onCitrus;
3517        if (taskbarSection) taskbarSection.hidden = !onCitrus;
3518        if (windowsSection) windowsSection.hidden = !onCitrus;
3519        if (desktopSection) desktopSection.hidden = !onCitrus;
3520        if (wallpaperSection) wallpaperSection.hidden = !onCitrus;
3521    }
3522
3523    function bindCitrusWallpaperSlider(panel, sliderId, labelId, options) {
3524        options = options || {};
3525        var slider = panel.querySelector('#' + sliderId);
3526        var label = panel.querySelector('#' + labelId);
3527        if (!slider) return;
3528
3529        function formatValue(raw) {
3530            if (typeof options.format === 'function') return options.format(raw);
3531            if (options.suffix) return raw + options.suffix;
3532            return String(raw);
3533        }
3534
3535        slider.addEventListener('input', function () {
3536            var next = clampSettingNumber(slider.value, options.min, options.max, options.fallback);
3537            currentSettings[options.key] = next;
3538            applyCitrusDesktopPrefs();
3539            if (label) label.textContent = formatValue(String(next));
3540        });
3541        slider.addEventListener('change', function () {
3542            var next = clampSettingNumber(slider.value, options.min, options.max, options.fallback);
3543            setSetting(options.key, next);
3544            slider.value = String(next);
3545            if (label) label.textContent = formatValue(String(next));
3546        });
3547    }
3548
3549    function bindDesktopWallpaperSliders(panel) {
3550        bindCitrusWallpaperSlider(panel, 'setting-citrusWallpaperDim', 'setting-citrusWallpaperDim-label', {
3551            key: 'citrusWallpaperDim',
3552            min: 0,
3553            max: 50,
3554            fallback: 0,
3555            suffix: '%'
3556        });
3557        bindCitrusWallpaperSlider(panel, 'setting-citrusWallpaperSaturation', 'setting-citrusWallpaperSaturation-label', {
3558            key: 'citrusWallpaperSaturation',
3559            min: 50,
3560            max: 150,
3561            fallback: 100,
3562            suffix: '%'
3563        });
3564        bindCitrusWallpaperSlider(panel, 'setting-citrusWallpaperBrightness', 'setting-citrusWallpaperBrightness-label', {
3565            key: 'citrusWallpaperBrightness',
3566            min: 50,
3567            max: 150,
3568            fallback: 100,
3569            suffix: '%'
3570        });
3571        bindCitrusWallpaperSlider(panel, 'setting-citrusWallpaperBlur', 'setting-citrusWallpaperBlur-label', {
3572            key: 'citrusWallpaperBlur',
3573            min: 0,
3574            max: 12,
3575            fallback: 0,
3576            format: function (raw) { return raw + ' px'; }
3577        });
3578
3579        var resetBtn = panel.querySelector('#settings-citrus-wallpaper-reset');
3580        if (resetBtn) {
3581            resetBtn.addEventListener('click', function () {
3582                setSetting('citrusWallpaperDim', DEFAULT_SETTINGS.citrusWallpaperDim);
3583                setSetting('citrusWallpaperSaturation', DEFAULT_SETTINGS.citrusWallpaperSaturation);
3584                setSetting('citrusWallpaperBrightness', DEFAULT_SETTINGS.citrusWallpaperBrightness);
3585                setSetting('citrusWallpaperBlur', DEFAULT_SETTINGS.citrusWallpaperBlur);
3586                updateSettingsUI();
3587            });
3588        }
3589    }
3590
3591    function bindDesktopIconActionButtons(panel) {
3592        var resetBtn = panel.querySelector('#settings-desktop-reset-icons');
3593        if (resetBtn) {
3594            resetBtn.addEventListener('click', function () {
3595                if (window.CitrusDesktop && typeof window.CitrusDesktop.resetDesktopToDefaults === 'function') {
3596                    function runReset() {
3597                        window.CitrusDesktop.resetDesktopToDefaults();
3598                    }
3599                    if (window.CitrusShellDialogs && typeof window.CitrusShellDialogs.confirm === 'function') {
3600                        window.CitrusShellDialogs.confirm({
3601                            title: 'Reset desktop?',
3602                            message: 'Restore factory desktop shortcuts and folder layout?',
3603                            confirmLabel: 'Reset',
3604                            danger: true
3605                        }).then(function (ok) {
3606                            if (ok) runReset();
3607                        });
3608                        return;
3609                    }
3610                    if (window.confirm('Restore factory desktop shortcuts and folder layout?')) {
3611                        runReset();
3612                    }
3613                }
3614            });
3615        }
3616
3617        var arrangeBtn = panel.querySelector('#settings-desktop-auto-arrange');
3618        if (arrangeBtn) {
3619            arrangeBtn.addEventListener('click', function () {
3620                if (window.CitrusDesktop && typeof window.CitrusDesktop.resetDesktopIconLayout === 'function') {
3621                    window.CitrusDesktop.resetDesktopIconLayout();
3622                } else if (window.CitrusDesktop && typeof window.CitrusDesktop.autoArrangeDesktopIcons === 'function') {
3623                    window.CitrusDesktop.autoArrangeDesktopIcons();
3624                }
3625            });
3626        }
3627
3628        var folderResetBtn = panel.querySelector('#settings-desktop-reset-folder-layout');
3629        if (folderResetBtn) {
3630            folderResetBtn.addEventListener('click', function () {
3631                if (window.CitrusDesktop && typeof window.CitrusDesktop.resetFolderLayout === 'function') {
3632                    window.CitrusDesktop.resetFolderLayout();
3633                }
3634            });
3635        }
3636    }
3637
3638    function onSettingsWindowOpen() {
3639        const panel = document.getElementById('settings-panel');
3640        if (!panel) return;
3641
3642        syncSettingsPanelChatroomTheme();
3643        syncSettingsPanelCitrusTheme();
3644        applyChatroomSiteSettingsNotice();
3645
3646        if (isChatroomPage() && window.ChatroomSettings && typeof window.ChatroomSettings.getColorMode === 'function') {
3647            const dark = window.ChatroomSettings.getColorMode() === 'dark';
3648            updateDarkModeUI(dark);
3649            const siteToggle = panel.querySelector('#setting-darkMode');
3650            if (siteToggle) siteToggle.checked = dark;
3651        }
3652
3653        updateSettingsUI();
3654        updateCloudsBackgroundWarning();
3655        if (!backgroundTuningUi && window.SiteBackgroundTuning) {
3656            mountBackgroundTuning(panel);
3657        } else if (backgroundTuningUi && typeof backgroundTuningUi.setActiveBackground === 'function') {
3658            setBackgroundTuningActive(currentSettings.background || DEFAULT_SETTINGS.background);
3659        }
3660
3661        updateAccountIndicator();
3662
3663        try {
3664            document.dispatchEvent(new CustomEvent('pupwife:settings-opened'));
3665        } catch (e) { /* ignore */ }
3666
3667        emitDevLog('Settings window opened');
3668    }
3669
3670    function openSettings() {
3671        if (isCitrusOsPage() && window.CitrusSettingsWindow && typeof window.CitrusSettingsWindow.open === 'function') {
3672            window.CitrusSettingsWindow.open();
3673            return;
3674        }
3675
3676        const backdrop = document.getElementById('settings-backdrop');
3677        const panel = document.getElementById('settings-panel');
3678        if (!backdrop || !panel) return;
3679
3680        backdrop.classList.add('settings-backdrop--open');
3681        panel.classList.add('settings-panel--open');
3682        onSettingsWindowOpen();
3683        emitDevLog('Settings menu opened');
3684    }
3685
3686    function isLoggedInAccount() {
3687        const username = window.__sharedUsername ? window.__sharedUsername.get() : null;
3688        const hasPassword = localStorage.getItem('puppy_username_password') !== null;
3689        const isGuestMode = localStorage.getItem('puppy_guest_mode') === 'true';
3690        return !!(username && hasPassword && !isGuestMode);
3691    }
3692
3693    function openAccountSignIn() {
3694        if (window.__sharedUsername && typeof window.__sharedUsername.promptAgain === 'function') {
3695            window.__sharedUsername.promptAgain();
3696        } else {
3697            window.location.href = 'account.html';
3698        }
3699    }
3700
3701    function updateAccountIndicator() {
3702        const card = document.getElementById('account-access-card');
3703        const logoutBtn = document.getElementById('settings-logout');
3704        if (!card) return;
3705
3706        if (isLoggedInAccount()) {
3707            const username = window.__sharedUsername.get();
3708            card.innerHTML =
3709                '<p class="settings-card__label"><i class="fa-solid fa-circle-user fa-width-auto" aria-hidden="true"></i> Logged in as</p>' +
3710                '<p class="settings-card__value">' + escapeHtml(username) + '</p>' +
3711                '<a href="account.html" class="settings-card__link">' +
3712                'View account <i class="fa-solid fa-arrow-right fa-width-auto" aria-hidden="true"></i></a>';
3713            if (logoutBtn) logoutBtn.style.display = '';
3714        } else {
3715            card.innerHTML =
3716                '<p class="settings-card__label"><i class="fa-solid fa-circle-user fa-width-auto" aria-hidden="true"></i> Account</p>' +
3717                '<p class="settings-section__hint" style="margin: 0.35rem 0 0.75rem;">Browsing as a guest is fine! Create an account anytime to save scores and use the chatroom.</p>' +
3718                '<button type="button" id="settings-sign-in" class="settings-card__link settings-card__link--button">' +
3719                '<i class="fa-solid fa-right-to-bracket fa-width-auto" aria-hidden="true"></i> Sign in / Create account</button>' +
3720                '<a href="account.html" class="settings-card__link" style="margin-top: 0.45rem;">Account page <i class="fa-solid fa-arrow-right fa-width-auto" aria-hidden="true"></i></a>';
3721            const signInBtn = card.querySelector('#settings-sign-in');
3722            if (signInBtn) {
3723                signInBtn.addEventListener('click', function () {
3724                    openAccountSignIn();
3725                });
3726            }
3727            if (logoutBtn) logoutBtn.style.display = 'none';
3728        }
3729
3730        if (window.AccountIndicator && typeof window.AccountIndicator.update === 'function') {
3731            window.AccountIndicator.update();
3732        }
3733        if (typeof window.syncSidebarAccountRow === 'function') {
3734            window.syncSidebarAccountRow();
3735        }
3736    }
3737
3738    function escapeHtml(text) {
3739        if (text == null) return '';
3740        const div = document.createElement('div');
3741        div.textContent = String(text);
3742        return div.innerHTML;
3743    }
3744
3745    function closeSettings() {
3746        if (isCitrusOsPage() && window.CitrusSettingsWindow && typeof window.CitrusSettingsWindow.isOpen === 'function' && window.CitrusSettingsWindow.isOpen()) {
3747            window.CitrusSettingsWindow.close();
3748            emitDevLog('Settings window closed');
3749            return;
3750        }
3751
3752        if (window.PupwifeTrailPicker && typeof window.PupwifeTrailPicker.close === 'function') {
3753            window.PupwifeTrailPicker.close();
3754        }
3755        if (window.PupwifeCursorPicker && typeof window.PupwifeCursorPicker.close === 'function') {
3756            window.PupwifeCursorPicker.close();
3757        }
3758        const backdrop = document.getElementById('settings-backdrop');
3759        const panel = document.getElementById('settings-panel');
3760        if (!panel) return;
3761
3762        if (backdrop) backdrop.classList.remove('settings-backdrop--open');
3763        panel.classList.remove('settings-panel--open');
3764
3765        try {
3766            document.dispatchEvent(new CustomEvent('pupwife:settings-closed'));
3767        } catch (e) {
3768            document.dispatchEvent(new Event('pupwife:settings-closed'));
3769        }
3770
3771        emitDevLog('Settings menu closed');
3772    }
3773
3774    function updateDarkModeUI(isDark) {
3775        const panel = document.getElementById('settings-panel');
3776        if (!panel) return;
3777
3778        const toggle = panel.querySelector('#setting-darkMode');
3779        const themeCard = panel.querySelector('#settings-theme-card');
3780
3781        if (toggle) toggle.checked = isDark;
3782        if (themeCard) themeCard.classList.toggle('is-dark', isDark);
3783    }
3784
3785    function updateSettingsUI() {
3786        const panel = document.getElementById('settings-panel');
3787        if (!panel) return;
3788
3789        // Check if we're on chat page
3790        const isOnChatPage = window.location.pathname.includes('chat.html') || 
3791                            window.location.pathname.endsWith('chat');
3792        
3793        // Update checkboxes
3794        for (const key in currentSettings) {
3795            if (key === 'trailAmount') continue;
3796            const checkbox = panel.querySelector(`#setting-${key}`);
3797            if (checkbox && checkbox.type === 'checkbox') {
3798                checkbox.checked = currentSettings[key];
3799            }
3800        }
3801        
3802        // Update dark mode UI (on chatroom, reflect chatroom theme — site darkMode toggle does not apply there)
3803        updateDarkModeUI(isChatroomPage() ? isChatroomDarkTheme() : currentSettings.darkMode);
3804        
3805        const citrusScaleRadio = panel.querySelector('input[name="citrusUiScale"][value="' + String(normalizeCitrusUiScale(currentSettings.citrusUiScale)) + '"]');
3806        if (citrusScaleRadio) {
3807            citrusScaleRadio.checked = true;
3808        }
3809
3810        const desktopScaleRadio = panel.querySelector('input[name="desktopIconScale"][value="' + String(normalizeDesktopIconScale(currentSettings.desktopIconScale)) + '"]');
3811        if (desktopScaleRadio) {
3812            desktopScaleRadio.checked = true;
3813        }
3814
3815        const musicPreloadRadio = panel.querySelector('input[name="musicPreload"][value="' + normalizeMusicPreload(currentSettings.musicPreload) + '"]');
3816        if (musicPreloadRadio) {
3817            musicPreloadRadio.checked = true;
3818        }
3819
3820        const dimSlider = panel.querySelector('#setting-citrusWallpaperDim');
3821        const dimLabel = panel.querySelector('#setting-citrusWallpaperDim-label');
3822        if (dimSlider) {
3823            dimSlider.value = String(currentSettings.citrusWallpaperDim || 0);
3824            if (dimLabel) dimLabel.textContent = dimSlider.value + '%';
3825        }
3826
3827        const satSlider = panel.querySelector('#setting-citrusWallpaperSaturation');
3828        const satLabel = panel.querySelector('#setting-citrusWallpaperSaturation-label');
3829        if (satSlider) {
3830            satSlider.value = String(currentSettings.citrusWallpaperSaturation != null
3831                ? currentSettings.citrusWallpaperSaturation
3832                : DEFAULT_SETTINGS.citrusWallpaperSaturation);
3833            if (satLabel) satLabel.textContent = satSlider.value + '%';
3834        }
3835
3836        const brightnessSlider = panel.querySelector('#setting-citrusWallpaperBrightness');
3837        const brightnessLabel = panel.querySelector('#setting-citrusWallpaperBrightness-label');
3838        if (brightnessSlider) {
3839            brightnessSlider.value = String(currentSettings.citrusWallpaperBrightness != null
3840                ? currentSettings.citrusWallpaperBrightness
3841                : DEFAULT_SETTINGS.citrusWallpaperBrightness);
3842            if (brightnessLabel) brightnessLabel.textContent = brightnessSlider.value + '%';
3843        }
3844
3845        const blurSlider = panel.querySelector('#setting-citrusWallpaperBlur');
3846        const blurLabel = panel.querySelector('#setting-citrusWallpaperBlur-label');
3847        if (blurSlider) {
3848            blurSlider.value = String(currentSettings.citrusWallpaperBlur != null
3849                ? currentSettings.citrusWallpaperBlur
3850                : DEFAULT_SETTINGS.citrusWallpaperBlur);
3851            if (blurLabel) blurLabel.textContent = blurSlider.value + ' px';
3852        }
3853
3854        // Update follower radio buttons
3855        // On chat page, show the saved preference but keep it disabled
3856        const followerRadio = panel.querySelector(`input[name="follower"][value="${currentSettings.follower}"]`);
3857        if (followerRadio && !isOnChatPage) {
3858            followerRadio.checked = true;
3859        } else if (isOnChatPage) {
3860            // On chat page, check "none" but show the user's actual preference in the UI
3861            const noneRadio = panel.querySelector(`input[name="follower"][value="none"]`);
3862            if (noneRadio) {
3863                noneRadio.checked = true;
3864            }
3865            // Also visually indicate the user's saved preference
3866            if (followerRadio) {
3867                var savedLabel = followerRadio.closest('.settings-radio');
3868                if (savedLabel) {
3869                    savedLabel.classList.add('is-saved-preference');
3870                    savedLabel.title = 'This is your saved preference (will resume on other pages)';
3871                }
3872            }
3873        }
3874        
3875        // Update cursor trail picker
3876        setCursorTrailPickerValue(currentSettings.cursorTrail);
3877
3878        const trailAmountSlider = panel.querySelector('#setting-trailAmount');
3879        if (trailAmountSlider) {
3880            trailAmountSlider.value = String(currentSettings.trailAmount);
3881            updateTrailAmountLabel(panel);
3882        }
3883        updateTrailControlsState();
3884
3885        setCustomCursorPickerValue(currentSettings.customCursor);
3886
3887        setBackgroundPickerValue(currentSettings.background || DEFAULT_SETTINGS.background);
3888
3889        // Update weather overlay checkbox
3890        const weatherOverlayCheckbox = panel.querySelector('#setting-weatherOverlay');
3891        if (weatherOverlayCheckbox) {
3892            weatherOverlayCheckbox.checked = currentSettings.weatherOverlay || false;
3893        }
3894        
3895        // Disable/enable visual effects based on performance mode
3896        const visualSection = panel.querySelector('#visual-effects-section');
3897        const isPerformanceMode = currentSettings.performanceMode;
3898        
3899        if (visualSection) {
3900            visualSection.querySelectorAll('input[type="checkbox"]').forEach(checkbox => {
3901                checkbox.disabled = isPerformanceMode;
3902                var row = checkbox.closest('.settings-row');
3903                if (row) row.classList.toggle('is-disabled', isPerformanceMode);
3904            });
3905        }
3906        
3907        const regenerateBtn = document.getElementById('regenerate-skeletile-btn');
3908        if (regenerateBtn) {
3909            regenerateBtn.classList.toggle('is-visible', currentSettings.follower === 'skeletile');
3910        }
3911        
3912        const regenerateDragonBtn = document.getElementById('regenerate-dragon-btn');
3913        if (regenerateDragonBtn) {
3914            regenerateDragonBtn.classList.toggle('is-visible', currentSettings.follower === 'dragon');
3915        }
3916
3917        syncWoimSettingsUI(panel);
3918        syncJellypuffSettingsUI(panel);
3919        syncCatPawSettingsUI(panel);
3920        updateTrailColorPanelVisibility();
3921        syncTrailColorPickers();
3922        applyNewFeatureBadges(panel);
3923    }
3924
3925    function handleDarkModeToggle(e) {
3926        const value = e.target.checked;
3927
3928        if (isChatroomPage()) {
3929            if (window.ChatroomSettings && typeof window.ChatroomSettings.setColorMode === 'function') {
3930                window.ChatroomSettings.setColorMode(value ? 'dark' : 'light');
3931            } else {
3932                setSetting('darkMode', value);
3933                document.dispatchEvent(new CustomEvent('pupwife-color-mode', {
3934                    detail: { colorMode: value ? 'dark' : 'light', darkMode: value }
3935                }));
3936            }
3937            updateDarkModeUI(value);
3938            emitDevLog(`Chatroom color mode: ${value ? 'dark' : 'light'}`);
3939            return;
3940        }
3941
3942        const isOnThemeExcludedPage = /cookbook/.test(window.location.pathname);
3943        if (isOnThemeExcludedPage) {
3944            setSetting('darkMode', value);
3945            updateDarkModeUI(value);
3946            emitDevLog(`Dark mode: ${value ? 'ON' : 'OFF'} (saved for other pages)`);
3947            return;
3948        }
3949
3950        const isDark = !!value;
3951        currentSettings.darkMode = isDark;
3952        if (window.SiteThemeToggle && typeof window.SiteThemeToggle.setDarkMode === 'function') {
3953            window.SiteThemeToggle.setDarkMode(isDark, 'settings');
3954        } else {
3955            setSetting('darkMode', isDark);
3956            updateDarkModeUI(isDark);
3957        }
3958        emitDevLog(`Dark mode: ${isDark ? 'ON' : 'OFF'}`);
3959    }
3960
3961    function handleSettingChange(e) {
3962        const settingKey = e.target.id.replace('setting-', '');
3963        const value = e.target.checked;
3964        
3965        setSetting(settingKey, value);
3966        updateSettingsUI();
3967        
3968        emitDevLog(`Setting changed: ${settingKey} = ${value}`);
3969    }
3970
3971    function handleCitrusUiScaleChange(e) {
3972        var scale = normalizeCitrusUiScale(e.target.value);
3973        setSetting('citrusUiScale', scale);
3974        applyCitrusUiScale(scale);
3975        updateSettingsUI();
3976        emitDevLog('CitrusOS UI scale: ' + scale + '%');
3977    }
3978
3979    function handleDesktopIconScaleChange(e) {
3980        var scale = normalizeDesktopIconScale(e.target.value);
3981        setSetting('desktopIconScale', scale);
3982        applyCitrusDesktopPrefs();
3983        updateSettingsUI();
3984        emitDevLog('Desktop icon scale: ' + scale + '%');
3985    }
3986
3987    function handleMusicPreloadChange(e) {
3988        var mode = normalizeMusicPreload(e.target.value);
3989        setSetting('musicPreload', mode);
3990        updateSettingsUI();
3991        emitDevLog('Music preload: ' + mode);
3992    }
3993
3994    function handleFollowerChange(e) {
3995        // Check if we're on chat page
3996        const isOnChatPage = window.location.pathname.includes('chat.html') || 
3997                            window.location.pathname.endsWith('chat');
3998        
3999        // Don't allow changes on chat page
4000        if (isOnChatPage) {
4001            emitDevLog('⚠️ Cannot change follower on chat page');
4002            e.preventDefault();
4003            return;
4004        }
4005        
4006        const followerType = e.target.value;
4007        setSetting('follower', followerType);
4008        
4009        // Show/hide Skeletile regenerate button
4010        const regenerateBtn = document.getElementById('regenerate-skeletile-btn');
4011        if (regenerateBtn) {
4012            regenerateBtn.classList.toggle('is-visible', followerType === 'skeletile');
4013        }
4014        
4015        const regenerateDragonBtn = document.getElementById('regenerate-dragon-btn');
4016        if (regenerateDragonBtn) {
4017            regenerateDragonBtn.classList.toggle('is-visible', followerType === 'dragon');
4018        }
4019
4020        updateWoimPanelVisibility(followerType);
4021        updateJellypuffPanelVisibility(followerType);
4022        
4023        emitDevLog(`Follower changed to: ${followerType}`);
4024    }
4025
4026    function applyNewFeatureBadges(panel) {
4027        if (!panel || !window.PupwifeSettingsDiscoverability) return;
4028        var items = [
4029            { sel: '#settings-export', cat: 'settings', id: 'export-import' },
4030            { sel: '#settings-import', cat: 'settings', id: 'export-import' },
4031            { sel: '#settings-randomize-cursor', cat: 'settings', id: 'randomize-cursor' }
4032        ];
4033        items.forEach(function (item) {
4034            var el = panel.querySelector(item.sel);
4035            if (!el || el.querySelector('.settings-feature-new')) return;
4036            if (!window.PupwifeSettingsDiscoverability.isNew(item.cat, item.id)) return;
4037            var badge = document.createElement('span');
4038            badge.className = 'settings-feature-new';
4039            badge.textContent = 'NEW!';
4040            el.appendChild(badge);
4041            el.addEventListener('click', function () {
4042                window.PupwifeSettingsDiscoverability.markSeen(item.cat, item.id);
4043                if (badge.parentNode) badge.remove();
4044            }, { once: true });
4045        });
4046    }
4047
4048    function resetSettings() {
4049        if (confirm('reset all settings to defaults? this cannot be undone!')) {
4050            currentSettings = Object.assign({ ...DEFAULT_SETTINGS }, {
4051                woimString: mergeWoimString({}),
4052                jellypuff: mergeJellypuff({}),
4053                catPawArm: mergeCatPawArm({}),
4054                trailColors: window.PupwifeTrailColorSupport
4055                    ? window.PupwifeTrailColorSupport.mergeTrailColors(null)
4056                    : { primary: '#c8a8ff', secondary: '#ffb3ba', tertiary: '#89cff0' },
4057                trailFavorites: []
4058            });
4059            saveSettings();
4060            applySettings();
4061            updateSettingsUI();
4062            emitDevLog('Settings reset to defaults!');
4063        }
4064    }
4065
4066    // ====================================
4067    // INITIALIZATION
4068    // ====================================
4069
4070    function syncCurrentSettingsFromStorage() {
4071        currentSettings = loadSettings();
4072        try {
4073            const raw = localStorage.getItem('puppywifey_settings');
4074            if (raw) {
4075                const parsed = JSON.parse(raw);
4076                if (parsed && typeof parsed === 'object' &&
4077                    Object.prototype.hasOwnProperty.call(parsed, 'darkMode') &&
4078                    typeof parsed.darkMode !== 'boolean') {
4079                    parsed.darkMode = parseDarkMode(parsed.darkMode, DEFAULT_SETTINGS.darkMode);
4080                    localStorage.setItem('puppywifey_settings', JSON.stringify(parsed));
4081                }
4082            }
4083        } catch (e) {}
4084    }
4085
4086    function init() {
4087        function boot() {
4088            syncCurrentSettingsFromStorage();
4089            ensureSettingsFontAwesome()
4090                .then(ensureSettingsHelperScripts)
4091                .then(ensurePalettePickerScript)
4092                .then(ensureBackgroundPickerScript)
4093                .then(ensureTrailPickerScript)
4094                .then(ensureCursorPickerScript)
4095                .then(ensureWoimColorPanelScript)
4096                .then(function () {
4097                    createSettingsUI();
4098                    applySettings();
4099                });
4100        }
4101
4102        if (document.readyState === 'loading') {
4103            document.addEventListener('DOMContentLoaded', boot);
4104        } else {
4105            boot();
4106        }
4107    }
4108
4109    // ====================================
4110    // GLOBAL API
4111    // ====================================
4112
4113    window.SettingsMenu = {
4114        getSetting,
4115        setSetting,
4116        setSettingQuiet,
4117        loadSettings,
4118        mergeWoimString,
4119        setWoimString,
4120        saveSettings,
4121        openSettings: openSettings,
4122        closeSettings: closeSettings,
4123        onWindowOpen: onSettingsWindowOpen,
4124        applyWallpaperFilters: applyWallpaperFilters
4125    };
4126
4127    document.addEventListener('pupwife-color-mode', function (e) {
4128        if (!e.detail) return;
4129        updateDarkModeUI(e.detail.colorMode === 'dark');
4130        const toggle = document.getElementById('setting-darkMode');
4131        if (toggle) {
4132            toggle.checked = e.detail.colorMode === 'dark';
4133        }
4134        if (e.detail.palette) {
4135            currentSettings.palette = e.detail.palette;
4136            setPalettePickerValue(e.detail.palette);
4137        }
4138    });
4139
4140    document.addEventListener('pupwife-site-theme', function (e) {
4141        if (!e.detail) return;
4142        if (typeof e.detail.darkMode === 'boolean') {
4143            if (e.detail.source !== 'settings') {
4144                currentSettings.darkMode = e.detail.darkMode;
4145                updateDarkModeUI(e.detail.darkMode);
4146            }
4147            if (e.detail.source === 'home-landing') {
4148                return;
4149            }
4150        }
4151        if (e.detail.palette) {
4152            currentSettings.palette = e.detail.palette;
4153            setPalettePickerValue(e.detail.palette);
4154        }
4155    });
4156
4157    // Start!
4158    init();
4159
vendor: 5 bytes, line 4160
4160})();

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.