PageSourceSearch

https://pupwife.com/scripts/desktop/appRegistry.js?v=kittysensory-fixed-20260909

js pupwife.com collected 2026-09-30 15:04:19 UTC 16,844 bytes, 371 lines download raw bytes

1/**
2 * CitrusOS app registry — desktop / Start menu apps and launch tiers.
3 */
4(function (global) {
5    'use strict';
6
7    /** Apps that leave CitrusOS and open as a full page (too heavy for in-shell loader). */
8    var EXTERNAL_LAUNCH = {};
9
10    var DEFAULT_SIZE = { w: 920, h: 640 };
11
12    /** Apps that support F11 / titlebar maximize immersive focus inside CitrusOS. */
13    var LEAF_IMMERSIVE_FOCUS = {
14        'pupocalypse.html': true,
15        'daw.html': true
16    };
17
18    /** Apps that support F11 immersive focus only (no titlebar maximize button). */
19    var LEAF_IMMERSIVE_KEYBOARD = {
20        'dnd.html': true
21    };
22
23    var LEAF_NO_RESIZE = {
24        'daw.html': true,
25        'noisegenerator.html': true,
26        'palette.html': true,
27        'gradientgenerator.html': true,
28        'frameflipper.html': true,
29        'whoshere.html': true,
30        'trimmer.html': true,
31        'workout.html': true,
32        'plong.html': true,
33        'kittysensory.html': true,
34        'cookbook.html': true,
35        'studyhall.html': true,
36        'terrarium.html': true,
37        'dnd.html': true,
38        'taskboard.html': true,
39        'adminpanel.html': true,
40        'aboutme.html': true,
41        'contact.html': true,
42        'terms.html': true,
43        'privacy.html': true,
44        'attributions.html': true,
45        'bookings.html': true,
46        'changelog.html': true
47    };
48
49    var LEAF_DEFAULT_SIZES = {
50        'pupocalypse.html': { w: 900, h: 560 },
51        'weather.html': { w: 520, h: 640 },
52        'sortlab.html': { w: 720, h: 520 },
53        'timezones.html': { w: 900, h: 620 },
54        'trimmer.html': { w: 500, h: 540 },
55        'normalMapping.html': { w: 760, h: 560 },
56        'uvmapping.html': { w: 800, h: 560 },
57        'mememaker.html': { w: 820, h: 600 },
58        'music.html': { w: 900, h: 600 },
59        'daw.html': { w: 1000, h: 380 },
60        'strudel.html': { w: 960, h: 640 },
61        'noisegenerator.html': { w: 300, h: 175 },
62        'palette.html': { w: 520, h: 370 },
63        'gradientgenerator.html': { w: 400, h: 300 },
64        'frameflipper.html': { w: 880, h: 720 },
65        'whoshere.html': { w: 300, h: 480 },
66        'workout.html': { w: 680, h: 536 },
67        'terrarium.html': { w: 680, h: 540 },
68        'plong.html': { w: 420, h: 580 },
69        'kittysensory.html': { w: 1000, h: 720 },
70        'cookbook.html': { w: 1100, h: 620 },
71        'studyhall.html': { w: 800, h: 600 },
72        'dnd.html': { w: 1100, h: 720 },
73        'taskboard.html': { w: 900, h: 640 },
74        'adminpanel.html': { w: 880, h: 580 },
75        'aboutme.html': { w: 420, h: 580 },
76        'contact.html': { w: 420, h: 580 },
77        'terms.html': { w: 420, h: 580 },
78        'privacy.html': { w: 420, h: 580 },
79        'attributions.html': { w: 420, h: 580 },
80        'bookings.html': { w: 420, h: 580 },
81        'changelog.html': { w: 480, h: 560 }
82    };
83
84    var LEAF_LAUNCH_TIERS = {
85        'puppypaint.html': 'fullscreen'
86    };
87
88    var DESKTOP_APPS = [
89        { id: 'arcade', label: 'Arcade', href: '/arcade.html', iconType: 'citrus-folder', launchTier: 'folder', folderId: 'arcade', defaultSize: { w: 680, h: 500 }, resizable: false },
90        { id: 'silly', label: 'Silly Things', href: '/silly.html', iconType: 'citrus-folder', launchTier: 'folder', folderId: 'silly', defaultSize: { w: 680, h: 500 }, resizable: false },
91        { id: 'casino', label: 'Casino', href: '/casino.html', iconType: 'citrus-folder', launchTier: 'folder', folderId: 'casino', defaultSize: { w: 760, h: 560 }, resizable: false },
92        { id: 'shrines', label: 'Shrines', href: '/shrines.html', iconType: 'citrus-folder', launchTier: 'folder', folderId: 'shrines', defaultSize: { w: 560, h: 420 }, resizable: false },
93        { id: 'stickynotes', label: 'Sticky Notes', href: '/stickynotes.html', iconType: 'citrus-folder', launchTier: 'folder', folderId: 'stickynotes', defaultSize: { w: 520, h: 420 }, resizable: false },
94        { id: 'siteinfo', label: 'Site Info', href: '/siteinfo.html', iconType: 'citrus-folder', launchTier: 'folder', folderId: 'siteinfo', defaultSize: { w: 560, h: 480 }, resizable: false },
95        { id: 'music', label: 'Music', href: '/music.html', iconAsset: 'general/app_music.png', icon: 'fa-solid fa-music', iconType: 'fa', launchTier: 'window', defaultSize: { w: 900, h: 600 }, singleton: true, resizable: false },
96        { id: 'friends', label: 'Friends', href: '/friends.html', iconAsset: 'general/app_friends.png', icon: 'fa-solid fa-user-group', iconType: 'fa', launchTier: 'window' },
97        { id: 'blog', label: 'Blog', href: '/blog.html', icon: 'fa-solid fa-newspaper', iconType: 'fa', launchTier: 'window', defaultSize: { w: 760, h: 620 }, resizable: false },
98        { id: 'studyhall', label: 'Study Hall', href: '/studyhall.html', iconAsset: 'general/app_studyhall.png', icon: 'fa-solid fa-graduation-cap', iconType: 'fa', launchTier: 'window', defaultSize: { w: 800, h: 600 }, resizable: false },
99        { id: 'pupocalypse', label: 'Pupocalypse', href: '/pupocalypse.html', iconAsset: 'games/app_pupocalypse.png', icon: 'fa-solid fa-gamepad', iconType: 'fa', launchTier: 'window', defaultSize: { w: 900, h: 560 }, singleton: true, immersiveFocus: true },
100        { id: 'cookbook', label: 'Cookbook', href: '/cookbook.html', iconAsset: 'general/app_cookbook.png', icon: 'fa-solid fa-book', iconType: 'fa', launchTier: 'window', defaultSize: { w: 1100, h: 620 }, singleton: true, resizable: false },
101        { id: 'daw', label: 'Virtual Keyboard', href: '/daw.html', icon: 'fa-solid fa-keyboard', iconType: 'fa', launchTier: 'window', defaultSize: { w: 1000, h: 380 }, singleton: true, immersiveFocus: true, resizable: false },
102        { id: 'strudel', label: 'Strudel', href: '/strudel.html', icon: 'fa-solid fa-code', iconType: 'fa', launchTier: 'window', defaultSize: { w: 960, h: 640 }, singleton: true, resizable: true },
103        { id: 'chat', label: 'Chat', href: '/chatroom.html', icon: 'fa-solid fa-comments', iconType: 'fa', launchTier: 'window', defaultSize: { w: 420, h: 560 }, singleton: true, resizable: false },
104        { id: 'plong', label: 'Plong', href: '/plong.html', iconAsset: 'games/app_plong.png', icon: 'fa-solid fa-table-tennis-paddle-ball', iconType: 'fa', launchTier: 'window', defaultSize: { w: 420, h: 580 }, singleton: true, resizable: false },
105        { id: 'kittysensory', label: 'Kitty Sensory Corner', href: '/kittysensory.html', icon: 'fa-solid fa-paw', iconType: 'fa', launchTier: 'window', defaultSize: { w: 1000, h: 720 }, singleton: true, resizable: false },
106        { id: 'taskboard', label: 'Task Board', href: '/taskboard.html', icon: 'fa-solid fa-clipboard-list', iconType: 'fa', launchTier: 'window', defaultSize: { w: 900, h: 640 }, singleton: true, resizable: false },
107        { id: 'changelog', label: 'Changelog', href: '/changelog.html', icon: 'fa-solid fa-clock-rotate-left', iconType: 'fa', launchTier: 'window', defaultSize: { w: 480, h: 560 }, singleton: true, resizable: false, startMenuHidden: true }
108    ];
109
110    /** Owner-only desktop icon — not listed in Start menu search. */
111    var OWNER_ONLY_APPS = [
112        {
113            id: 'moderation',
114            label: 'Moderation',
115            windowLabel: 'Admin Panel',
116            href: '/adminpanel.html',
117            icon: 'fa-solid fa-computer',
118            iconType: 'fa',
119            launchTier: 'window',
120            defaultSize: { w: 880, h: 580 },
121            singleton: true,
122            resizable: false,
123            ownerOnly: true,
124            startMenuHidden: true,
125            windowClass: 'citrus-window--admin'
126        }
127    ];
128
129    var OWNER_NAME = 'puppy';
130
131    function isSiteOwner() {
132        try {
133            var username = localStorage.getItem('puppy_shared_username') || '';
134            return username.trim().toLowerCase() === OWNER_NAME;
135        } catch (e) {
136            return false;
137        }
138    }
139
140    function normalizeHref(href) {
141        if (!href) return '';
142        var key = href.replace(/^\//, '').split('?')[0].split('#')[0];
143        if (!/\.html$/i.test(key) && key.indexOf('.') === -1) key += '.html';
144        return '/' + key;
145    }
146
147    function hrefToId(href) {
148        var key = normalizeHref(href).replace(/^\//, '');
149        return key.replace(/\.html$/i, '').replace(/\//g, '-');
150    }
151
152    function enrichApp(app) {
153        var entry = {
154            id: app.id,
155            label: app.label,
156            href: normalizeHref(app.href),
157            icon: app.icon,
158            iconAsset: app.iconAsset || null,
159            iconType: app.iconType || null,
160            launchTier: app.launchTier || 'window',
161            folderId: app.folderId || null,
162            folderPath: app.folderPath || null,
163            noteId: app.noteId || null,
164            defaultSize: app.defaultSize || DEFAULT_SIZE,
165            singleton: app.singleton !== false,
166            immersiveFocus: !!app.immersiveFocus,
167            immersiveKeyboard: !!app.immersiveKeyboard,
168            resizable: app.launchTier === 'folder' ? false : (app.resizable !== false),
169            ownerOnly: !!app.ownerOnly,
170            startMenuHidden: !!app.startMenuHidden,
171            windowLabel: app.windowLabel || null,
172            windowClass: app.windowClass || null
173        };
174        if (global.CitrusFolderIcon && typeof global.CitrusFolderIcon.normalizeFolderApp === 'function') {
175            global.CitrusFolderIcon.normalizeFolderApp(entry);
176        }
177        return entry;
178    }
179
180    function buildApps() {
181        var apps = DESKTOP_APPS.map(enrichApp);
182        if (isSiteOwner()) {
183            apps = apps.concat(OWNER_ONLY_APPS.map(enrichApp));
184        }
185        return apps;
186    }
187
188    function getStartMenuApps() {
189        return buildApps().filter(function (app) {
190            return !app.startMenuHidden;
191        });
192    }
193
194    function getApp(id) {
195        var apps = buildApps();
196        for (var i = 0; i < apps.length; i++) {
197            if (apps[i].id === id) return apps[i];
198        }
199        if (id && id.indexOf('folder:') === 0) {
200            var folderId = id.replace('folder:', '');
201            var meta = global.CitrusFolderRegistry && global.CitrusFolderRegistry.FOLDER_META
202                ? global.CitrusFolderRegistry.FOLDER_META[folderId]
203                : null;
204            if (meta) {
205                return enrichApp({
206                    id: id,
207                    label: meta.label,
208                    href: '/' + folderId + '.html',
209                    icon: meta.icon,
210                    iconType: meta.iconType || 'citrus-folder',
211                    launchTier: 'folder',
212                    folderId: folderId,
213                    defaultSize: folderId === 'casino' ? { w: 760, h: 560 } : { w: 680, h: 500 }
214                });
215            }
216        }
217        return null;
218    }
219
220    function getAppById(id, hrefHint) {
221        if (!id) return null;
222        var app = getApp(id);
223        if (app) return app;
224        if (hrefHint) {
225            app = getAppByHref(hrefHint);
226            if (app) return app;
227        }
228        if (id.indexOf('-') !== -1) {
229            app = getAppByHref('/' + id.replace(/-/g, '/') + '.html');
230            if (app) return app;
231        }
232        return getAppByHref('/' + id + '.html');
233    }
234
235    function getAppByHref(href) {
236        var key = normalizeHref(href);
237        var apps = buildApps();
238        for (var i = 0; i < apps.length; i++) {
239            if (apps[i].href === key) return apps[i];
240        }
241        return getLeafApp(href);
242    }
243
244    function getLeafApp(href) {
245        var key = normalizeHref(href).replace(/^\//, '');
246        var fileKey = key;
247        var label = key.replace(/\.html$/i, '').replace(/casino\//, '').replace(/\//g, ' ');
248        var icon = 'fa-solid fa-window-maximize';
249        var external = !!EXTERNAL_LAUNCH[fileKey.split('/').pop()];
250        var fileName = fileKey.split('/').pop();
251        var leafSize = LEAF_DEFAULT_SIZES[fileName] || DEFAULT_SIZE;
252        var launchTier = LEAF_LAUNCH_TIERS[fileName] || 'window';
253        var immersiveFocus = !!LEAF_IMMERSIVE_FOCUS[fileName];
254        var immersiveKeyboard = !!LEAF_IMMERSIVE_KEYBOARD[fileName];
255        var resizable = !LEAF_NO_RESIZE[fileName];
256
257        if (global.CitrusFolderRegistry && global.CitrusFolderRegistry.findAppInFolders) {
258            var found = global.CitrusFolderRegistry.findAppInFolders(href);
259            if (found) {
260                label = found.label;
261                if (found.iconType === 'fa') icon = found.icon;
262                external = found.external;
263            }
264        }
265
266        if (global.ArcadeHubMeta && global.ArcadeHubMeta.getGame) {
267            var arcadeGame = global.ArcadeHubMeta.getGame(fileKey);
268            if (arcadeGame) {
269                label = arcadeGame.title;
270                icon = 'fa-solid ' + (arcadeGame.icon || 'fa-gamepad');
271            }
272        }
273
274        return enrichApp({
275            id: hrefToId(href),
276            label: label,
277            href: normalizeHref(href),
278            icon: icon.indexOf('/') === 0 || icon.indexOf('http') === 0 ? icon : icon,
279            launchTier: external ? 'external' : launchTier,
280            defaultSize: leafSize,
281            singleton: true,
282            immersiveFocus: immersiveFocus,
283            immersiveKeyboard: immersiveKeyboard,
284            resizable: resizable
285        });
286    }
287
288    function matchesFolderIndexItem(item, q) {
289        if (!q) return false;
290        if (item.label.toLowerCase().indexOf(q) !== -1) return true;
291        if (item.href && item.href.toLowerCase().indexOf(q) !== -1) return true;
292        if (item.ancestors && item.ancestors.length) {
293            if (item.ancestors.join(' ').toLowerCase().indexOf(q) !== -1) return true;
294        }
295        if (item.path && item.path.length && item.path.join('/').toLowerCase().indexOf(q) !== -1) return true;
296        return false;
297    }
298
299    function searchApps(query) {
300        var q = (query || '').trim().toLowerCase();
301        var results = [];
302        var seen = {};
303
304        function pushResult(entry) {
305            if (!entry || seen[entry.id]) return;
306            seen[entry.id] = true;
307            results.push(entry);
308        }
309
310        var apps = getStartMenuApps();
311        for (var i = 0; i < apps.length; i++) {
312            var app = apps[i];
313            if (!q ||
314                app.label.toLowerCase().indexOf(q) !== -1 ||
315                app.id.toLowerCase().indexOf(q) !== -1 ||
316                app.href.toLowerCase().indexOf(q) !== -1) {
317                pushResult(app);
318            }
319        }
320
321        if (q && global.CitrusFolderRegistry && typeof global.CitrusFolderRegistry.flattenSearchIndex === 'function') {
322            var index = global.CitrusFolderRegistry.flattenSearchIndex();
323            var folderMeta = global.CitrusFolderRegistry.FOLDER_META || {};
324            for (var j = 0; j < index.length; j++) {
325                var item = index[j];
326                if (!matchesFolderIndexItem(item, q)) continue;
327                if (item.type === 'app') {
328                    pushResult(getLeafApp(item.href));
329                } else if (item.type === 'note') {
330                    pushResult(enrichApp({
331                        id: 'note:' + item.noteId,
332                        label: item.label + ' (Sticky Notes)',
333                        href: '/stickynotes.html',
334                        icon: item.icon || 'fa-solid fa-file-lines',
335                        launchTier: 'note',
336                        noteId: item.noteId,
337                        singleton: false
338                    }));
339                } else if (item.type === 'folder') {
340                    var pathKey = item.path && item.path.length ? item.path.join('/') : '';
341                    var hubLabel = (folderMeta[item.folderId] || {}).label || item.folderId;
342                    pushResult(enrichApp({
343                        id: 'folder:' + item.folderId + (pathKey ? ':' + pathKey : ''),
344                        label: item.label + ' (' + hubLabel + ')',
345                        href: '/' + item.folderId + '.html',
346                        icon: item.icon,
347                        launchTier: 'folder',
348                        folderId: item.folderId,
349                        folderPath: item.path ? item.path.slice() : []
350                    }));
351                }
352            }
353        }
354
355        return results;
356    }
357
358    global.CitrusAppRegistry = {
359        getApps: buildApps,
360        getStartMenuApps: getStartMenuApps,
361        isSiteOwner: isSiteOwner,
362        getApp: getApp,
363        getAppById: getAppById,
364        getAppByHref: getAppByHref,
365        getLeafApp: getLeafApp,
366        search: searchApps,
367        isExternalLaunch: function (routeId) {
368            return !!EXTERNAL_LAUNCH[routeId.replace(/^\//, '').split('/').pop()];
369        }
370    };
371})(typeof window !== 'undefined' ? window : this);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.