PageSourceSearch

https://pupwife.com/scripts/effects/siteContextMenuRegistry.js?v=

js pupwife.com collected 2026-09-30 15:06:01 UTC 56,809 bytes, 1,371 lines download raw bytes

1/**
2 * Default + page-specific items for SiteContextMenu.
3 */
4(function (global) {
5    'use strict';
6
7    var SCM = null;
8
9    var NAV_ICONS = {
10        Home: 'fa-solid fa-house',
11        Music: 'fa-solid fa-music',
12        Friends: 'fa-solid fa-user-group',
13        'Silly Things': 'fa-solid fa-face-laugh-beam',
14        Cookbook: 'fa-solid fa-book',
15        DAW: 'fa-solid fa-sliders',
16        Shrines: 'fa-solid fa-star',
17        Arcade: 'fa-solid fa-gamepad',
18        Casino: 'fa-solid fa-dice',
19        'Study Hall': 'fa-solid fa-graduation-cap',
20        Blog: 'fa-solid fa-pen-nib',
21        Chat: 'fa-solid fa-comments',
22        Dashboard: 'fa-solid fa-house'
23    };
24
25    var FOLLOWER_OPTIONS = [
26        { id: 'none', label: 'None', icon: 'fa-solid fa-ban' },
27        { id: 'marsey', label: 'Marsey', icon: 'fa-solid fa-cat' },
28        { id: 'skeletile', label: 'Skeletile', icon: 'fa-solid fa-skull' },
29        { id: 'orca', label: 'Orca', icon: 'fa-solid fa-fish-fins' },
30        { id: 'dragon', label: 'Dragon', icon: 'fa-solid fa-dragon' },
31        { id: 'fish2D', label: '2D fish', icon: 'fa-solid fa-fish' },
32        { id: 'woimString', label: 'Woim on a string', icon: 'fa-solid fa-worm' },
33        { id: 'jellypuff', label: 'Jellypuff', icon: 'fa-solid fa-droplet' }
34    ];
35
36    var registryBooted = false;
37    var registryBootAttempts = 0;
38    var REGISTRY_BOOT_MAX_ATTEMPTS = 120;
39
40    function shellPrompt(label, defaultValue) {
41        if (global.CitrusShellDialogs && typeof global.CitrusShellDialogs.prompt === 'function') {
42            return global.CitrusShellDialogs.prompt({
43                title: label,
44                label: label,
45                defaultValue: defaultValue || ''
46            });
47        }
48        return Promise.resolve(global.prompt(label, defaultValue));
49    }
50
51    function shellConfirm(opts) {
52        if (typeof opts === 'string') opts = { message: opts };
53        if (global.CitrusShellDialogs && typeof global.CitrusShellDialogs.confirm === 'function') {
54            return global.CitrusShellDialogs.confirm(opts);
55        }
56        return Promise.resolve(global.confirm(opts.message || ''));
57    }
58
59    function api() {
60        return SCM || global.SiteContextMenu;
61    }
62
63    function isCitrusOsPage() {
64        if (global.document.body && global.document.body.classList.contains('page-citrusos')) {
65            return true;
66        }
67        return !!global.document.getElementById('citrusos-shell');
68    }
69
70    function setting(key, fallback) {
71        var a = api();
72        return a ? a.getSetting(key, fallback) : fallback;
73    }
74
75    function setSetting(key, value) {
76        var a = api();
77        if (a) a.setSetting(key, value);
78    }
79
80    function getSidebarNavEntries() {
81        if (global.SidebarNavConfig && global.SidebarNavConfig.items) {
82            return global.SidebarNavConfig.items;
83        }
84        return [
85            { href: '/index.html', label: 'Home' },
86            { href: '/music.html', label: 'Music' },
87            { href: '/friends.html', label: 'Friends' },
88            { href: '/silly.html', label: 'Silly Things' },
89            { href: '/cookbook.html', label: 'Cookbook' },
90            { href: '/daw.html', label: 'Virtual Keyboard' },
91            { href: '/strudel.html', label: 'Strudel' },
92            { href: '/shrines.html', label: 'Shrines' },
93            { href: '/arcade.html', label: 'Arcade' },
94            { href: '/casino.html', label: 'Casino' },
95            { href: '/?app=studyhall', label: 'Study Hall' },
96            { href: '/blog.html', label: 'Blog' },
97            { href: '/chatroom.html', label: 'Chat' }
98        ];
99    }
100
101    function buildNavigateSubmenuChildren(ctx) {
102        return getSidebarNavEntries().map(function (entry) {
103            var routeId = api().hrefToRouteId(entry.href);
104            return {
105                id: 'nav-' + routeId,
106                label: entry.label,
107                icon: NAV_ICONS[entry.label] || 'fa-solid fa-arrow-right',
108                when: function (c) { return c.routeId !== routeId; },
109                action: function () { api().navigate(routeId); }
110            };
111        });
112    }
113
114    function buildFollowerSubmenuChildren() {
115        return FOLLOWER_OPTIONS.map(function (opt) {
116            return {
117                id: 'follower-' + opt.id,
118                label: opt.label,
119                icon: opt.icon,
120                checked: function () { return setting('follower', 'none') === opt.id; },
121                action: function () {
122                    setSetting('follower', opt.id);
123                    if (global.switchFollower) {
124                        global.switchFollower(opt.id, true);
125                    }
126                    api().showToast('Follower: ' + opt.label);
127                }
128            };
129        });
130    }
131
132    function clickRandom(selector, emptyMsg) {
133        var nodes = global.document.querySelectorAll(selector);
134        if (!nodes.length) {
135            api().showToast(emptyMsg || 'Nothing to pick right now');
136            return;
137        }
138        nodes[Math.floor(Math.random() * nodes.length)].click();
139    }
140
141    function copyMusicNowPlaying() {
142        if (!global.musicPlayerApi || typeof global.musicPlayerApi.getCurrentIndex !== 'function') {
143            api().showToast('Music player not ready');
144            return;
145        }
146        var idx = global.musicPlayerApi.getCurrentIndex();
147        var meta = global.musicPlayerApi.getTrackMeta(idx);
148        if (!meta || !meta.title) {
149            api().showToast('No track loaded');
150            return;
151        }
152        var line = meta.title + (meta.artist ? ' by ' + meta.artist : '');
153        api().copyText(line, 'Now playing copied');
154    }
155
156    function citrusContextClosest(ctx, selector) {
157        if (api() && typeof api().contextClosest === 'function') {
158            return api().contextClosest(ctx, selector);
159        }
160        if (!ctx) return null;
161        var event = ctx.event;
162        if (event && typeof event.composedPath === 'function') {
163            var path = event.composedPath();
164            for (var i = 0; i < path.length; i++) {
165                var node = path[i];
166                if (node && node.nodeType === 1 && node.matches && node.matches(selector)) {
167                    return node;
168                }
169            }
170        }
171        var el = ctx.target;
172        if (!el) return null;
173        if (el.nodeType === 3 && el.parentElement) el = el.parentElement;
174        return el && el.closest ? el.closest(selector) : null;
175    }
176
177    function citrusIsAppPinnable(app) {
178        if (!app) return false;
179        var tier = app.launchTier || 'window';
180        return tier === 'window' || tier === 'folder';
181    }
182
183    function citrusRegistryApp(id, hrefHint) {
184        var registry = global.CitrusAppRegistry;
185        if (!registry || !id) return null;
186        if (registry.getAppById) return registry.getAppById(id, hrefHint);
187        return registry.getApp ? registry.getApp(id) : null;
188    }
189
190    function resolveCitrusPinnableApp(ctx, knownApp) {
191        if (knownApp && citrusIsAppPinnable(knownApp)) return knownApp;
192        var registry = global.CitrusAppRegistry;
193        if (!registry) return null;
194
195        var desktopIcon = citrusContextClosest(ctx, '.citrus-desktop-icon');
196        if (desktopIcon) {
197            var desktopAppId = desktopIcon.getAttribute('data-app-id');
198            var desktopApp = citrusRegistryApp(desktopAppId);
199            if (citrusIsAppPinnable(desktopApp)) return desktopApp;
200        }
201
202        var taskbarBtn = citrusContextClosest(ctx, '.citrus-taskbar-app');
203        if (taskbarBtn) {
204            var taskbarAppId = taskbarBtn.getAttribute('data-app-id');
205            var taskbarApp = citrusRegistryApp(taskbarAppId);
206            if (citrusIsAppPinnable(taskbarApp)) return taskbarApp;
207        }
208
209        var startAppBtn = citrusContextClosest(ctx, '.citrus-start-app');
210        if (startAppBtn) {
211            var startAppId = startAppBtn.getAttribute('data-app-id');
212            var startApp = citrusRegistryApp(startAppId);
213            if (citrusIsAppPinnable(startApp)) return startApp;
214        }
215
216        var folderIcon = citrusContextClosest(ctx, '.citrus-folder-icon');
217        if (folderIcon && folderIcon.getAttribute('data-entry-type') === 'app') {
218            var href = folderIcon.getAttribute('data-entry-href');
219            if (href && registry.getAppByHref) {
220                var folderApp = registry.getAppByHref(href);
221                if (citrusIsAppPinnable(folderApp)) return folderApp;
222            }
223        }
224
225        if (global.CitrusDesktop && typeof global.CitrusDesktop.resolvePinnableAppFromElement === 'function') {
226            var el = ctx.target;
227            if (el && el.nodeType === 3 && el.parentElement) el = el.parentElement;
228            var fromDesktop = global.CitrusDesktop.resolvePinnableAppFromElement(el);
229            if (citrusIsAppPinnable(fromDesktop)) return fromDesktop;
230        }
231
232        return null;
233    }
234
235    function readCitrusPinnedApps() {
236        try {
237            var raw = localStorage.getItem('citrusos_session_v3');
238            var session = raw ? JSON.parse(raw) : {};
239            return Array.isArray(session.pinnedApps) ? session.pinnedApps : [];
240        } catch (e) {
241            return [];
242        }
243    }
244
245    function isCitrusAppPinnedLocal(appId) {
246        return readCitrusPinnedApps().indexOf(appId) !== -1;
247    }
248
249    function appendCitrusPinTaskbarItems(items, ctx, knownApp) {
250        if (!isCitrusOsPage() || !ctx) return false;
251        var app;
252        try {
253            app = resolveCitrusPinnableApp(ctx, knownApp);
254        } catch (err) {
255            if (typeof global.devWarn === 'function') {
256                global.devWarn('[SiteContextMenuRegistry] Pin resolve failed', err);
257            }
258            return false;
259        }
260        if (!app) return false;
261        var appId = app.id;
262        var pinned = isCitrusAppPinnedLocal(appId);
263        items.push({
264            id: pinned ? 'citrus-pin-unpin' : 'citrus-pin-taskbar',
265            label: pinned ? 'Unpin from taskbar' : 'Pin to taskbar',
266            icon: pinned ? 'fa-solid fa-xmark' : 'fa-solid fa-thumbtack',
267            action: function () {
268                if (!global.CitrusDesktop) return;
269                if (pinned && typeof global.CitrusDesktop.unpinApp === 'function') {
270                    global.CitrusDesktop.unpinApp(appId);
271                } else if (!pinned && typeof global.CitrusDesktop.pinApp === 'function') {
272                    global.CitrusDesktop.pinApp(appId);
273                }
274            }
275        });
276        return true;
277    }
278
279    function resolveCitrusWindowIdFromCtx(ctx) {
280        if (!ctx) return '';
281        if (ctx.citrusWindowId) return ctx.citrusWindowId;
282        if (ctx.citrusWindow) return ctx.citrusWindow.getAttribute('data-app-id') || '';
283        if (ctx.citrusTaskbarBtn) {
284            return ctx.citrusTaskbarBtn.getAttribute('data-window-id') ||
285                ctx.citrusTaskbarBtn.getAttribute('data-app-id') || '';
286        }
287        return '';
288    }
289
290    function citrusOnWindowChrome(ctx) {
291        if (!isCitrusOsPage() || !ctx) return false;
292        if (ctx.citrusOnTitlebarChrome) return true;
293        return !!(ctx.citrusTaskbarBtn &&
294            !citrusContextClosest(ctx, '.citrus-desktop-icon') &&
295            !citrusContextClosest(ctx, '.citrus-folder-icon'));
296    }
297
298    function citrusInAppWindow(ctx) {
299        if (!isCitrusOsPage() || !ctx) return false;
300        if (citrusOnWindowChrome(ctx)) return false;
301        if (citrusContextClosest(ctx, '.citrus-desktop-icon')) return false;
302        if (citrusContextClosest(ctx, '.citrus-widget')) return false;
303        if (citrusContextClosest(ctx, '.citrus-folder-window')) return false;
304        if (citrusContextClosest(ctx, '.page-chatroom')) return false;
305        if (ctx.citrusOnDesktop && !citrusContextClosest(ctx, '.citrus-window')) return false;
306        return !!citrusContextClosest(ctx, '.citrus-window__content, .citrus-fullscreen__content');
307    }
308
309    function appendDndFloatChromeItems(items, ctx) {
310        var container = ctx && ctx.dndFloatContainer;
311        if (!container || !global.CitrusDesktop) return false;
312        var desktop = global.CitrusDesktop;
313        var pinned = typeof desktop.isLayerPinnedTop === 'function' && desktop.isLayerPinnedTop(container);
314        items.push({
315            id: pinned ? 'dnd-float-unpin-front' : 'dnd-float-pin-front',
316            label: pinned ? 'Unpin from front' : 'Pin to front',
317            icon: 'fa-solid fa-thumbtack',
318            checked: pinned,
319            defaultFocus: true,
320            action: function () {
321                if (typeof desktop.toggleLayerPinnedTop === 'function') {
322                    desktop.toggleLayerPinnedTop(container);
323                }
324                if (global.DndWorkspace && typeof global.DndWorkspace.syncFloatHostPinState === 'function') {
325                    global.DndWorkspace.syncFloatHostPinState();
326                }
327            }
328        });
329        return true;
330    }
331
332    function appendCitrusWindowChromeItems(items, ctx) {
333        var windowId = resolveCitrusWindowIdFromCtx(ctx);
334        if (!windowId || !global.CitrusDesktop) return false;
335        var desktop = global.CitrusDesktop;
336        var state = typeof desktop.getWindowState === 'function'
337            ? desktop.getWindowState(windowId)
338            : { open: false, minimized: false };
339        var win = typeof desktop.getWindow === 'function' ? desktop.getWindow(windowId) : null;
340        var app = win && win.app ? win.app : null;
341
342        if (state.open && win && typeof desktop.isWindowPinnedTop === 'function') {
343            var pinnedTop = desktop.isWindowPinnedTop(windowId);
344            items.push({
345                id: pinnedTop ? 'citrus-win-unpin-front' : 'citrus-win-pin-front',
346                label: pinnedTop ? 'Unpin from front' : 'Pin to front',
347                icon: 'fa-solid fa-thumbtack',
348                checked: pinnedTop,
349                action: function () {
350                    if (typeof desktop.toggleWindowPinnedTop === 'function') {
351                        desktop.toggleWindowPinnedTop(windowId);
352                    }
353                }
354            });
355        }
356
357        if (state.open && state.minimized) {
358            items.push({
359                id: 'citrus-win-restore',
360                label: 'Restore',
361                icon: 'fa-solid fa-window-restore',
362                defaultFocus: true,
363                action: function () {
364                    if (typeof desktop.restoreWindow === 'function') desktop.restoreWindow(windowId);
365                }
366            });
367        } else if (state.open) {
368            items.push({
369                id: 'citrus-win-minimize',
370                label: 'Minimize',
371                icon: 'fa-solid fa-window-minimize',
372                defaultFocus: true,
373                action: function () {
374                    if (typeof desktop.minimizeWindow === 'function') {
375                        desktop.minimizeWindow(windowId);
376                    } else if (typeof desktop.minimizeApp === 'function') {
377                        desktop.minimizeApp(windowId);
378                    }
379                }
380            });
381        }
382
383        if (state.open && win) {
384            if (win.immersive) {
385                items.push({
386                    id: 'citrus-win-exit-immersive',
387                    label: 'Exit fullscreen',
388                    icon: 'fa-solid fa-compress',
389                    action: function () {
390                        if (typeof desktop.toggleImmersiveFocus === 'function') {
391                            desktop.toggleImmersiveFocus(windowId);
392                        }
393                    }
394                });
395            } else if (win.maximized) {
396                items.push({
397                    id: 'citrus-win-restore-size',
398                    label: 'Restore down',
399                    icon: 'fa-solid fa-window-restore',
400                    action: function () {
401                        if (typeof desktop.toggleMaximize === 'function') {
402                            desktop.toggleMaximize(windowId);
403                        }
404                    }
405                });
406            } else if (app && app.resizable !== false && app.anchored !== true) {
407                items.push({
408                    id: 'citrus-win-maximize',
409                    label: app && app.immersiveFocus ? 'Fullscreen' : 'Maximize',
410                    icon: app && app.immersiveFocus ? 'fa-solid fa-expand' : 'fa-solid fa-window-maximize',
411                    action: function () {
412                        if (app && app.immersiveFocus &&
413                            typeof desktop.toggleImmersiveFocus === 'function') {
414                            desktop.toggleImmersiveFocus(windowId);
415                        } else if (typeof desktop.toggleMaximize === 'function') {
416                            desktop.toggleMaximize(windowId);
417                        }
418                    }
419                });
420            }
421        }
422
423        if (state.open) {
424            items.push({
425                id: 'citrus-win-close',
426                label: 'Close',
427                icon: 'fa-solid fa-xmark',
428                danger: true,
429                action: function () {
430                    if (typeof desktop.requestCloseWindow === 'function') {
431                        desktop.requestCloseWindow(windowId);
432                    } else if (typeof desktop.closeWindow === 'function') {
433                        desktop.closeWindow(windowId);
434                    }
435                }
436            });
437        } else if (app && typeof desktop.openApp === 'function') {
438            items.push({
439                id: 'citrus-win-open',
440                label: 'Open',
441                icon: 'fa-solid fa-folder-open',
442                defaultFocus: true,
443                action: function () {
444                    desktop.openApp(app.id);
445                }
446            });
447        }
448
449        items.push({ type: 'divider' });
450        appendCitrusPinTaskbarItems(items, ctx, app);
451        return items.length > 0;
452    }
453
454    function appendCitrusSharedClipboardItems(items) {
455        items.push({
456            id: 'copy-selection',
457            label: 'Copy selected text',
458            icon: 'fa-solid fa-text-width',
459            when: function (c) { return !!c.selectionText; },
460            action: function (c) {
461                api().copyText(c.selectionText, 'Selection copied');
462            }
463        });
464
465        items.push({
466            id: 'copy-link',
467            label: 'Copy link address',
468            icon: 'fa-solid fa-link',
469            when: function (c) { return !!c.linkEl; },
470            action: function (c) {
471                api().copyText(c.linkEl.href, 'Link copied');
472            }
473        });
474
475        items.push({
476            id: 'open-link',
477            label: 'Open link in new tab',
478            icon: 'fa-solid fa-up-right-from-square',
479            when: function (c) {
480                return !!c.linkEl && !c.linkEl.target;
481            },
482            action: function (c) {
483                global.open(c.linkEl.href, '_blank', 'noopener,noreferrer');
484            }
485        });
486
487        items.push({
488            id: 'copy-image',
489            label: 'Copy image address',
490            icon: 'fa-solid fa-image',
491            when: function (c) { return !!c.imgEl && c.imgEl.src; },
492            action: function (c) {
493                api().copyText(c.imgEl.currentSrc || c.imgEl.src, 'Image URL copied');
494            }
495        });
496    }
497
498    function appendCitrusTrimmedWindowItems(items, ctx) {
499        items.push({
500            id: 'open-settings',
501            label: 'Open settings',
502            icon: 'fa-solid fa-gear',
503            defaultFocus: true,
504            action: function () {
505                if (global.SettingsMenu && global.SettingsMenu.openSettings) {
506                    global.SettingsMenu.openSettings();
507                }
508            }
509        });
510        appendCitrusSharedClipboardItems(items);
511    }
512
513    function registerGlobalItems() {
514        api().registerProvider(function (ctx) {
515            var items = [];
516            var inAppWindow = citrusInAppWindow(ctx);
517            var onWindowChrome = citrusOnWindowChrome(ctx);
518
519            var iconBtn = citrusContextClosest(ctx, '.citrus-desktop-icon');
520            if (iconBtn && isCitrusOsPage()) {
521                var shortcutId = iconBtn.getAttribute('data-shortcut-id');
522                var appId = iconBtn.getAttribute('data-app-id');
523                var bulkCount = 0;
524                var bulkMode = false;
525                if (global.CitrusDesktopSelection && global.CitrusDesktopSelection.getSelectedCount &&
526                    global.CitrusDesktopSelection.isSelected) {
527                    bulkCount = global.CitrusDesktopSelection.getSelectedCount();
528                    bulkMode = bulkCount > 1 && global.CitrusDesktopSelection.isSelected(shortcutId);
529                }
530                var citrusApp = null;
531                if (shortcutId && global.CitrusDesktopShortcuts) {
532                    var sc = global.CitrusDesktopShortcuts.getShortcutById(shortcutId);
533                    citrusApp = sc ? global.CitrusDesktopShortcuts.resolveShortcut(sc) : null;
534                }
535                if (!citrusApp && global.CitrusAppRegistry && global.CitrusAppRegistry.getApp) {
536                    citrusApp = global.CitrusAppRegistry.getApp(appId);
537                }
538                if (bulkMode) {
539                    items.push({
540                        id: 'citrus-icon-open-all',
541                        label: 'Open all (' + bulkCount + ')',
542                        icon: 'fa-solid fa-folder-open',
543                        defaultFocus: true,
544                        action: function () {
545                            if (global.CitrusDesktop && global.CitrusDesktop.openSelectedShortcuts) {
546                                global.CitrusDesktop.openSelectedShortcuts();
547                            }
548                        }
549                    });
550                    items.push({
551                        id: 'citrus-icon-remove-all',
552                        label: 'Remove all from desktop (' + bulkCount + ')',
553                        icon: 'fa-solid fa-trash-can',
554                        action: function () {
555                            if (global.CitrusDesktop && global.CitrusDesktop.removeSelectedShortcuts) {
556                                global.CitrusDesktop.removeSelectedShortcuts();
557                            }
558                        }
559                    });
560                    items.push({ type: 'divider' });
561                } else {
562                    items.push({
563                        id: 'citrus-icon-open',
564                        label: 'Open',
565                        icon: 'fa-solid fa-folder-open',
566                        defaultFocus: true,
567                        action: function () {
568                            if (global.CitrusDesktop && global.CitrusDesktop.openShortcut) {
569                                global.CitrusDesktop.openShortcut(shortcutId);
570                            } else if (global.CitrusDesktop && global.CitrusDesktop.openApp) {
571                                global.CitrusDesktop.openApp(appId);
572                            }
573                        }
574                    });
575                    appendCitrusPinTaskbarItems(items, ctx, citrusApp);
576                    items.push({
577                        id: 'citrus-icon-rename',
578                        label: 'Rename',
579                        icon: 'fa-solid fa-pen',
580                        action: function () {
581                            var current = iconBtn.querySelector('.citrus-desktop-icon__label');
582                            shellPrompt('Shortcut name', current ? current.textContent : '').then(function (next) {
583                                if (next == null) return;
584                                if (global.CitrusDesktop && global.CitrusDesktop.renameDesktopShortcut) {
585                                    global.CitrusDesktop.renameDesktopShortcut(shortcutId, next);
586                                }
587                            });
588                        }
589                    });
590                    items.push({
591                        id: 'citrus-icon-properties',
592                        label: 'Properties',
593                        icon: 'fa-solid fa-circle-info',
594                        action: function () {
595                            if (global.CitrusShortcutProperties && global.CitrusShortcutProperties.openForShortcut) {
596                                global.CitrusShortcutProperties.openForShortcut(shortcutId);
597                            }
598                        }
599                    });
600                    items.push({
601                        id: 'citrus-icon-remove',
602                        label: 'Remove from desktop',
603                        icon: 'fa-solid fa-trash-can',
604                        action: function () {
605                            if (global.CitrusDesktop && global.CitrusDesktop.removeDesktopShortcut) {
606                                global.CitrusDesktop.removeDesktopShortcut(shortcutId);
607                            }
608                        }
609                    });
610                    if (citrusApp && citrusApp.userFolderId) {
611                        items.push({
612                            id: 'citrus-userfolder-delete',
613                            label: 'Delete folder',
614                            icon: 'fa-solid fa-folder-minus',
615                            action: function () {
616                                var ufId = citrusApp.userFolderId;
617                                if (!global.CitrusUserFolders) return;
618                                function doDelete() {
619                                    global.CitrusUserFolders.deleteFolder(ufId, true);
620                                    if (global.CitrusDesktop && global.CitrusDesktop.removeDesktopShortcut) {
621                                        global.CitrusDesktop.removeDesktopShortcut(shortcutId);
622                                    }
623                                    if (global.CitrusDesktop && global.CitrusDesktop.refreshDesktopIcons) {
624                                        global.CitrusDesktop.refreshDesktopIcons();
625                                    }
626                                }
627                                if (global.CitrusUserFolders.countContents(ufId) > 0) {
628                                    shellConfirm({
629                                        title: 'Delete folder?',
630                                        message: 'This folder is not empty. Delete it anyway? Contents will return to default locations.',
631                                        confirmLabel: 'Delete',
632                                        danger: true
633                                    }).then(function (ok) {
634                                        if (ok) doDelete();
635                                    });
636                                    return;
637                                }
638                                doDelete();
639                            }
640                        });
641                    }
642                    items.push({ type: 'divider' });
643                }
644            }
645
646            var folderIcon = citrusContextClosest(ctx, '.citrus-folder-icon');
647            if (folderIcon && isCitrusOsPage() && !iconBtn) {
648                var entryType = folderIcon.getAttribute('data-entry-type');
649                var entryHref = folderIcon.getAttribute('data-entry-href');
650                var entryId = folderIcon.getAttribute('data-entry-id');
651                var userFolderId = folderIcon.getAttribute('data-user-folder-id');
652                var folderApp = entryType === 'app' && entryHref &&
653                    global.CitrusAppRegistry && global.CitrusAppRegistry.getAppByHref
654                    ? global.CitrusAppRegistry.getAppByHref(entryHref)
655                    : null;
656                if (entryType === 'app' && entryHref) {
657                    items.push({
658                        id: 'citrus-folder-icon-open',
659                        label: 'Open',
660                        icon: 'fa-solid fa-folder-open',
661                        defaultFocus: true,
662                        action: function () {
663                            if (global.CitrusDesktop && typeof global.CitrusDesktop.openHref === 'function') {
664                                global.CitrusDesktop.openHref(entryHref);
665                            }
666                        }
667                    });
668                    appendCitrusPinTaskbarItems(items, ctx, folderApp);
669                    items.push({
670                        id: 'citrus-folder-add-desktop',
671                        label: 'Add to desktop',
672                        icon: 'fa-solid fa-arrow-up-from-bracket',
673                        action: function () {
674                            if (!global.CitrusDesktopShortcuts) return;
675                            var key = global.CitrusDesktopShortcuts.targetKeyFromFolderEntry({
676                                type: entryType,
677                                href: entryHref,
678                                id: entryId
679                            });
680                            if (global.CitrusDesktop && global.CitrusDesktop.addDesktopShortcut) {
681                                global.CitrusDesktop.addDesktopShortcut(key);
682                            }
683                        }
684                    });
685                    items.push({ type: 'divider' });
686                } else if (entryType === 'note') {
687                    items.push({
688                        id: 'citrus-folder-note-desktop',
689                        label: 'Add to desktop',
690                        icon: 'fa-solid fa-arrow-up-from-bracket',
691                        action: function () {
692                            if (global.CitrusDesktop && global.CitrusDesktop.addDesktopShortcut) {
693                                global.CitrusDesktop.addDesktopShortcut('note:' + entryId);
694                            }
695                        }
696                    });
697                    items.push({ type: 'divider' });
698                } else if (entryType === 'folder') {
699                    items.push({
700                        id: 'citrus-folder-subfolder-desktop',
701                        label: 'Add to desktop',
702                        icon: 'fa-solid fa-arrow-up-from-bracket',
703                        action: function () {
704                            var key = userFolderId ? ('user:' + userFolderId) : entryId;
705                            if (global.CitrusDesktop && global.CitrusDesktop.addDesktopShortcut) {
706                                global.CitrusDesktop.addDesktopShortcut(key);
707                            }
708                        }
709                    });
710                    items.push({ type: 'divider' });
711                }
712            }
713
714            var folderShell = citrusContextClosest(ctx, '.citrus-folder-window');
715            if (folderShell && isCitrusOsPage() && !folderIcon && !iconBtn) {
716                var shellFolderId = folderShell.getAttribute('data-folder-id');
717                var folderPath = global.CitrusFolderWindow && global.CitrusFolderWindow.getFolderPath
718                    ? global.CitrusFolderWindow.getFolderPath(shellFolderId)
719                    : [];
720                items.push({
721                    id: 'citrus-folder-new-folder',
722                    label: 'New folder',
723                    icon: 'fa-solid fa-folder-plus',
724                    action: function () {
725                        shellPrompt('Folder name', 'New folder').then(function (name) {
726                            if (name == null) return;
727                            if (global.CitrusDesktop && global.CitrusDesktop.createNestedUserFolder) {
728                                global.CitrusDesktop.createNestedUserFolder(shellFolderId, folderPath, name);
729                            }
730                        });
731                    }
732                });
733                items.push({ type: 'divider' });
734            }
735
736            var desktopSurface = citrusContextClosest(ctx, '#citrusos-desktop');
737            if (desktopSurface && isCitrusOsPage() && !iconBtn && !folderIcon && !folderShell && !inAppWindow) {
738                items.unshift({
739                    id: 'citrus-desktop-new-folder',
740                    label: 'New folder',
741                    icon: 'fa-solid fa-folder-plus',
742                    action: function () {
743                        shellPrompt('Folder name', 'New folder').then(function (name) {
744                            if (name == null) return;
745                            if (global.CitrusDesktop && global.CitrusDesktop.createTopLevelUserFolder) {
746                                global.CitrusDesktop.createTopLevelUserFolder(name);
747                            }
748                        });
749                    }
750                });
751            }
752
753            var startAppBtn = citrusContextClosest(ctx, '.citrus-start-app');
754            if (startAppBtn && isCitrusOsPage() && !iconBtn && !folderIcon && !onWindowChrome) {
755                if (appendCitrusPinTaskbarItems(items, ctx)) {
756                    items.push({ type: 'divider' });
757                }
758            }
759
760            var widgetEl = citrusContextClosest(ctx, '.citrus-widget');
761            if (widgetEl && isCitrusOsPage()) {
762                var widgetId = widgetEl.getAttribute('data-widget-id');
763                var widgetType = widgetEl.getAttribute('data-widget-type');
764                items.push({
765                    id: 'citrus-widget-front',
766                    label: 'Bring widget to front',
767                    icon: 'fa-solid fa-layer-group',
768                    action: function () {
769                        if (global.CitrusWidgets && typeof global.CitrusWidgets.focusWidget === 'function') {
770                            global.CitrusWidgets.focusWidget(widgetId);
771                        }
772                    }
773                });
774                items.push({
775                    id: 'citrus-widget-remove',
776                    label: widgetType === 'note' ? 'Close note' : 'Remove widget',
777                    icon: widgetType === 'note' ? 'fa-solid fa-xmark' : 'fa-solid fa-tras
777h-can',
778                    action: function () {
779                        if (global.CitrusWidgets && typeof global.CitrusWidgets.removeWidget === 'function') {
780                            global.CitrusWidgets.removeWidget(widgetId);
781                        }
782                    }
783                });
784                items.push({ type: 'divider' });
785            }
786
787            if (ctx.dndFloatContainer && isCitrusOsPage()) {
788                var floatItems = [];
789                appendDndFloatChromeItems(floatItems, ctx);
790                return floatItems;
791            }
792
793            if (onWindowChrome) {
794                var chromeItems = [];
795                appendCitrusWindowChromeItems(chromeItems, ctx);
796                return chromeItems;
797            }
798
799            if (inAppWindow) {
800                appendCitrusTrimmedWindowItems(items, ctx);
801                return items;
802            }
803
804            items.push({
805                id: 'open-settings',
806                label: 'Open settings',
807                icon: 'fa-solid fa-gear',
808                defaultFocus: true,
809                action: function () {
810                    if (global.SettingsMenu && global.SettingsMenu.openSettings) {
811                        global.SettingsMenu.openSettings();
812                    }
813                }
814            });
815
816            if (isCitrusOsPage()) {
817                items = items.concat(buildCitrusDesktopMenuItems());
818            }
819
820            items.push({
821                id: 'navigate',
822                type: 'submenu',
823                label: isCitrusOsPage()
824                    ? 'Open app'
825                    : 'Navigate',
826                icon: isCitrusOsPage()
827                    ? 'fa-solid fa-window-maximize'
828                    : 'fa-solid fa-compass',
829                children: isCitrusOsPage()
830                    ? buildCitrusAppSubmenuChildren()
831                    : buildNavigateSubmenuChildren(ctx)
832            });
833
834            items.push({
835                id: 'followers',
836                type: 'submenu',
837                label: 'Cursor follower',
838                icon: 'fa-solid fa-paw',
839                children: buildFollowerSubmenuChildren()
840            });
841
842            appendCitrusSharedClipboardItems(items);
843
844            items.push({
845                id: 'reload',
846                label: 'Refresh',
847                icon: 'fa-solid fa-arrows-rotate',
848                action: function () { global.location.reload(); }
849            });
850
851            items.push({
852                id: 'hard-reload',
853                label: 'Hard Refresh',
854                icon: 'fa-solid fa-rotate',
855                action: function () {
856                    if (global.caches && global.caches.keys) {
857                        global.caches.keys().then(function (keys) {
858                            return Promise.all(keys.map(function (key) {
859                                return global.caches.delete(key);
860                            }));
861                        }).finally(function () {
862                            global.location.reload();
863                        });
864                        return;
865                    }
866                    global.location.reload();
867                }
868            });
869
870            items.push({
871                id: 'view-about',
872                label: 'About pupwife.com',
873                icon: 'fa-solid fa-circle-info',
874                action: function () {
875                    if (global.CitrusDesktop && typeof global.CitrusDesktop.openHref === 'function') {
876                        if (global.CitrusDesktop.openHref('/aboutme.html')) return;
877                    }
878                    global.location.href = 'index.html?open=aboutme.html';
879                }
880            });
881
882            items.push({ type: 'divider' });
883
884            items.push({
885                id: 'copy-url',
886                label: 'Copy page URL',
887                icon: 'fa-solid fa-link',
888                action: function () {
889                    api().copyText(global.location.href, 'Page URL copied');
890                }
891            });
892
893            items.push({
894                id: 'toggle-perf',
895                label: 'Performance mode',
896                icon: 'fa-solid fa-gauge-high',
897                checked: function () { return !!setting('performanceMode', false); },
898                action: function () {
899                    var next = !setting('performanceMode', false);
900                    setSetting('performanceMode', next);
901                    api().showToast(next ? 'Performance mode on' : 'Performance mode off');
902                }
903            });
904
905            return items;
906        });
907    }
908
909    function buildCitrusAppSubmenuChildren() {
910        try {
911            if (!global.CitrusAppRegistry || typeof global.CitrusAppRegistry.getApps !== 'function') {
912                return [];
913            }
914            var apps = global.CitrusAppRegistry.getApps();
915            if (!apps || !apps.length) return [];
916            return apps.map(function (app) {
917                return {
918                    id: 'citrus-open-' + app.id,
919                    label: app.label,
920                    icon: NAV_ICONS[app.label] || 'fa-solid fa-window-maximize',
921                    action: function () {
922                        if (global.CitrusDesktop && typeof global.CitrusDesktop.openApp === 'function') {
923                            global.CitrusDesktop.openApp(app.id);
924                        }
925                    }
926                };
927            });
928        } catch (err) {
929            if (typeof global.devWarn === 'function') {
930                global.devWarn('[SiteContextMenuRegistry] Citrus app submenu failed', err);
931            }
932            return [];
933        }
934    }
935
936    function buildCitrusWidgetSubmenuChildren() {
937        if (!global.CitrusWidgetRegistry || typeof global.CitrusWidgetRegistry.list !== 'function') {
938            return [];
939        }
940        var defs = global.CitrusWidgetRegistry.listGallery
941            ? global.CitrusWidgetRegistry.listGallery()
942            : global.CitrusWidgetRegistry.list().filter(function (def) { return !def.galleryHidden; });
943        if (!defs || !defs.length) return [];
944        return defs.map(function (def) {
945            return {
946                id: 'citrus-widget-add-' + def.type,
947                label: def.type === 'note' ? 'New note' : (def.label || def.type),
948                icon: def.icon || 'fa-solid fa-puzzle-piece',
949                action: function () {
950                    if (!global.CitrusWidgets) return;
951                    if (def.type === 'note' && typeof global.CitrusWidgets.createNoteAndOpen === 'function') {
952                        global.CitrusWidgets.createNoteAndOpen();
953                        return;
954                    }
955                    if (typeof global.CitrusWidgets.addWidget === 'function') {
956                        global.CitrusWidgets.addWidget(def.type);
957                    }
958                }
959            };
960        });
961    }
962
963    function buildCitrusDesktopMenuItems() {
964        return [
965            {
966                id: 'citrus-widget-gallery',
967                label: 'Widget gallery',
968                icon: 'fa-solid fa-puzzle-piece',
969                action: function () {
970                    if (global.CitrusWidgets && typeof global.CitrusWidgets.openGallery === 'function') {
971                        global.CitrusWidgets.openGallery();
972                    }
973                }
974            },
975            {
976                id: 'citrus-add-widget',
977                type: 'submenu',
978                label: 'Add widget',
979                icon: 'fa-solid fa-plus',
980                children: buildCitrusWidgetSubmenuChildren()
981            },
982            {
983                id: 'citrus-add-app',
984                label: 'Add app…',
985                icon: 'fa-solid fa-plus',
986                action: function () {
987                    if (global.CitrusAppPicker && typeof global.CitrusAppPicker.open === 'function') {
988                        global.CitrusAppPicker.open();
989                    }
990                }
991            },
992            {
993                id: 'citrus-desktop-new-folder-menu',
994                label: 'New folder',
995                icon: 'fa-solid fa-folder-plus',
996                action: function () {
997                    shellPrompt('Folder name', 'New folder').then(function (name) {
998                        if (name == null) return;
999                        if (global.CitrusDesktop && global.CitrusDesktop.createTopLevelUserFolder) {
1000                            global.CitrusDesktop.createTopLevelUserFolder(name);
1001                        }
1002                    });
1003                }
1004            },
1005            {
1006                type: 'divider'
1007            },
1008            {
1009                id: 'citrus-start-menu',
1010                label: 'Open Start menu',
1011                icon: 'fa-solid fa-bars',
1012                action: function () {
1013                    var btn = global.document.getElementById('citrusos-start-btn');
1014                    if (btn) btn.click();
1015                }
1016            },
1017            {
1018                id: 'citrus-music',
1019                label: 'Open Music',
1020                icon: 'fa-solid fa-music',
1021                action: function () {
1022                    if (global.CitrusDesktop && typeof global.CitrusDesktop.openApp === 'function') {
1023                        global.CitrusDesktop.openApp('music');
1024                    }
1025                }
1026            },
1027            {
1028                type: 'divider'
1029            },
1030            {
1031                id: 'citrus-reset-icon-layout',
1032                label: 'Reset icon layout',
1033                icon: 'fa-solid fa-grip',
1034                action: function () {
1035                    if (global.CitrusDesktop && typeof global.CitrusDesktop.resetDesktopIconLayout === 'function') {
1036                        global.CitrusDesktop.resetDesktopIconLayout();
1037                    }
1038                }
1039            },
1040            {
1041                id: 'citrus-reset-folder-layout',
1042                label: 'Reset folder layout',
1043                icon: 'fa-solid fa-folder-tree',
1044                action: function () {
1045                    if (global.CitrusDesktop && typeof global.CitrusDesktop.resetFolderLayout === 'function') {
1046                        global.CitrusDesktop.resetFolderLayout();
1047                    }
1048                }
1049            },
1050            {
1051                id: 'citrus-reset-desktop-defaults',
1052                label: 'Reset desktop to defaults',
1053                icon: 'fa-solid fa-rotate-left',
1054                action: function () {
1055                    shellConfirm({
1056                        title: 'Reset desktop?',
1057                        message: 'Restore factory desktop shortcuts and folder layout?',
1058                        confirmLabel: 'Reset',
1059                        danger: true
1060                    }).then(function (ok) {
1061                        if (!ok) return;
1062                        if (global.CitrusDesktop && typeof global.CitrusDesktop.resetDesktopToDefaults === 'function') {
1063                            global.CitrusDesktop.resetDesktopToDefaults();
1064                        }
1065                    });
1066                }
1067            },
1068            {
1069                id: 'citrus-reset-widgets',
1070                label: 'Reset widgets',
1071                icon: 'fa-solid fa-table-cells',
1072                action: function () {
1073                    if (global.CitrusWidgets && typeof global.CitrusWidgets.resetLayout === 'function') {
1074                        global.CitrusWidgets.resetLayout();
1075                    }
1076                }
1077            }
1078        ];
1079    }
1080
1081    function registerHomeItems() {
1082        api().registerPage('index.html', function () {
1083            if (isCitrusOsPage()) {
1084                return [];
1085            }
1086            return [
1087                {
1088                    id: 'skip-landing',
1089                    label: 'Skip landing animation',
1090                    icon: 'fa-solid fa-forward',
1091                    when: function () {
1092                        return global.HomeLanding && global.HomeLanding.isActive && global.HomeLanding.isActive();
1093                    },
1094                    action: function () {
1095                        global.HomeLanding.destroy();
1096                        api().showToast('Landing skipped');
1097                    }
1098                }
1099            ];
1100        });
1101    }
1102
1103    function registerMusicItems() {
1104        api().registerPage('music.html', function () {
1105            return [
1106                {
1107                    id: 'music-play-pause',
1108                    label: 'Play / pause',
1109                    icon: 'fa-solid fa-play',
1110                    when: function () { return typeof global.musicPlayerPlayPause === 'function'; },
1111                    action: function () { global.musicPlayerPlayPause(); }
1112                },
1113                {
1114                    id: 'music-copy-now-playing',
1115                    label: 'Copy now playing',
1116                    icon: 'fa-solid fa-music',
1117                    when: function () { return !!global.musicPlayerApi; },
1118                    action: copyMusicNowPlaying
1119                }
1120            ];
1121        });
1122    }
1123
1124    function registerFriendsItems() {
1125        api().registerPage('friends.html', function () {
1126            return [
1127                {
1128                    id: 'friends-random',
1129                    label: 'Visit random friend',
1130                    icon: 'fa-solid fa-shuffle',
1131                    when: function () {
1132                        return !!global.document.querySelector('.friends-float-container .friend-link');
1133                    },
1134                    action: function () {
1135                        clickRandom('.friends-float-container .friend-link', 'No friend links foun
1135d');
1136                    }
1137                }
1138            ];
1139        });
1140    }
1141
1142    function registerSillyItems() {
1143        api().registerPage('silly.html', function () {
1144            return [
1145                {
1146                    id: 'silly-random-tool',
1147                    label: 'Open random silly tool',
1148                    icon: 'fa-solid fa-dice',
1149                    action: function () {
1150                        clickRandom('#content .tool-button', 'No tools found');
1151                    }
1152                },
1153                {
1154                    id: 'silly-heathcliff',
1155                    label: 'View today\'s Heathcliff',
1156                    icon: 'fa-solid fa-newspaper',
1157                    when: function () {
1158                        var comic = global.document.getElementById('heathcliff-comic');
1159                        return comic && comic.style.display !== 'none' && comic.src;
1160                    },
1161                    action: function () {
1162                        var comic = global.document.getElementById('heathcliff-comic');
1163                        if (comic) comic.click();
1164                    }
1165                }
1166            ];
1167        });
1168    }
1169
1170    function registerCookbookItems() {
1171        api().registerPage('cookbook.html', function () {
1172            return [
1173                {
1174                    id: 'cookbook-metric',
1175                    label: 'Use metric units',
1176                    icon: 'fa-solid fa-weight-scale',
1177                    when: function () {
1178                        var btn = global.document.getElementById('unit-measure-toggle');
1179                        return !!btn && btn.textContent !== 'Metric';
1180                    },
1181                    action: function () {
1182                        var btn = global.document.getElementById('unit-measure-toggle');
1183                        if (btn) btn.click();
1184                        api().showToast('Metric units');
1185                    }
1186                },
1187                {
1188                    id: 'cookbook-imperial',
1189                    label: 'Use imperial units',
1190                    icon: 'fa-solid fa-kitchen-set',
1191                    when: function () {
1192                        var btn = global.document.getElementById('unit-measure-toggle');
1193                        return !!btn && btn.textContent !== 'Imperial';
1194                    },
1195                    action: function () {
1196                        var btn = global.document.getElementById('unit-measure-toggle');
1197                        if (btn) btn.click();
1198                        api().showToast('Imperial units');
1199                    }
1200                }
1201            ];
1202        });
1203    }
1204
1205    function registerShrinesItems() {
1206        api().registerPage('shrines.html', function () {
1207            return [
1208                {
1209                    id: 'shrines-random',
1210                    label: 'Open random shrine',
1211                    icon: 'fa-solid fa-star',
1212                    when: function () { return !!global.document.querySelector('.db-shrine-card'); },
1213                    action: function () {
1214                        clickRandom('.db-shrine-card', 'No shrines found');
1215                    }
1216                }
1217            ];
1218        });
1219    }
1220
1221    function registerArcadeItems() {
1222        api().registerPage('arcade.html', function () {
1223            return [
1224                {
1225                    id: 'arcade-random',
1226                    label: 'Open random game',
1227                    icon: 'fa-solid fa-dice',
1228                    action: function () {
1229                        clickRandom('#content a[href*=".html"], #content .db-widget a[href]', 'No game links found');
1230                    }
1231                }
1232            ];
1233        });
1234    }
1235
1236    function registerCasinoItems() {
1237        api().registerPage('casino.html', function () {
1238            return [
1239                {
1240                    id: 'casino-random-game',
1241                    label: 'Open random casino game',
1242                    icon: 'fa-solid fa-dice',
1243                    action: function () {
1244                        clickRandom('.casino-games .casino-game-link:not(.casino-game-link--wor
1244k)', 'No casino games found');
1245                    }
1246                },
1247                {
1248                    id: 'casino-daily-claim',
1249                    label: 'Claim daily coins',
1250                    icon: 'fa-solid fa-coins',
1251                    when: function () {
1252                        var btn = global.document.querySelector('[data-casino-daily-claim]');
1253                        return btn && !btn.hidden && !btn.disabled;
1254                    },
1255                    action: function () {
1256                        var btn = global.document.querySelector('[data-casino-daily-claim]');
1257                        if (btn) btn.click();
1258                    }
1259                },
1260                {
1261                    id: 'casino-sign-in',
1262                    label: 'Sign in to casino',
1263                    icon: 'fa-solid fa-right-to-bracket',
1264                    when: function () {
1265                        var banner = global.document.querySelector('[data-casino-guest-banner]');
1266                        return banner && !banner.hidden;
1267                    },
1268                    action: function () {
1269                        var btn = global.document.querySelector('[data-casino-sign-in]');
1270                        if (btn) btn.click();
1271                    }
1272                }
1273            ];
1274        });
1275    }
1276
1277    function registerStudyHallItems() {
1278        api().registerPage('studyhall.html', function () {
1279            return [
1280                {
1281                    id: 'studyhall-open',
1282                    label: 'Open Study Hall',
1283                    icon: 'fa-solid fa-graduation-cap',
1284                    when: function () {
1285                        return !!(global.CitrusDesktop && global.document.body.classList.contains('page-citrusos'));
1286                    },
1287                    action: function () {
1288                        global.CitrusDesktop.openApp('studyhall');
1289                    }
1290                }
1291            ];
1292        });
1293    }
1294
1295    function registerBlogItems() {
1296        api().registerPage('blog.html', function () {
1297            return [
1298                {
1299                    id: 'blog-random-post',
1300                    label: 'Open random blog post',
1301                    icon: 'fa-solid fa-shuffle',
1302                    when: function () { return !!global.document.querySelector('.blog-post-card'); },
1303                    action: function () {
1304                        clickRandom('.blog-post-card', 'No posts found');
1305                    }
1306                }
1307            ];
1308        });
1309    }
1310
1311    function registerToolItems() {
1312        api().registerPage('timezones.html', function () {
1313            return [
1314                {
1315                    id: 'tz-copy-board',
1316                    label: 'Copy meeting board text',
1317                    icon: 'fa-solid fa-copy',
1318                    when: function () {
1319                        return !!global.document.querySelector('.tz-board, #tz-board, [data-tz-board]');
1320                    },
1321                    action: function () {
1322                        var el = global.document.querySelector('.tz-board-output, [data-tz-export]');
1323                        if (el && el.textContent) {
1324                            api().copyText(el.textContent.trim(), 'Board copied');
1325                        } else {
1326                            api().showToast('Nothing to copy yet');
1327                        }
1328                    }
1329                }
1330            ];
1331        });
1332    }
1333
1334    function bootRegistry() {
1335        SCM = global.SiteContextMenu;
1336        if (!SCM || registryBooted) return;
1337        registryBooted = true;
1338
1339        registerGlobalItems();
1340        registerHomeItems();
1341        registerMusicItems();
1342        registerFriendsItems();
1343        registerSillyItems();
1344        registerCookbookItems();
1345        registerShrinesItems();
1346        registerArcadeItems();
1347        registerCasinoItems();
1348        registerStudyHallItems();
1349        registerBlogItems();
1350        registerToolItems();
1351    }
1352
1353    function ensureRegistryBoot() {
1354        if (global.SiteContextMenu) {
1355            bootRegistry();
1356            if (typeof global.SiteContextMenu.init === 'function') {
1357                global.SiteContextMenu.init();
1358            }
1359            return;
1360        }
1361        registryBootAttempts += 1;
1362        if (registryBootAttempts > REGISTRY_BOOT_MAX_ATTEMPTS) return;
1363        if (global.document.readyState === 'loading') {
1364            global.document.addEventListener('DOMContentLoaded', ensureRegistryBoot, { once: true });
1365            return;
1366        }
1367        global.setTimeout(ensureRegistryBoot, 50);
1368    }
1369
1370    ensureRegistryBoot();
1371})(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.