1/** 2 * CitrusOS Account window â retro-styled account panel (replaces account.html navigation on index). 3 */ 4(function (global) { 5 'use strict'; 6 7 var ACCOUNT_AUX_ID = 'account'; 8 var CSS_HREF = '/css/citrusAccount.css'; 9 var activeRoot = null; 10 11 function ensureCss() { 12 if (document.querySelector('link[data-citrus-account-css]')) return; 13 var link = document.createElement('link'); 14 link.rel = 'stylesheet'; 15 link.href = CSS_HREF; 16 link.setAttribute('data-citrus-account-css', '1'); 17 document.head.appendChild(link); 18 } 19 20 function isLoggedIn() { 21 if (global.AccountIndicator && typeof global.AccountIndicator.isLoggedIn === 'function') { 22 return global.AccountIndicator.isLoggedIn(); 23 } 24 var username = localStorage.getItem('puppy_shared_username'); 25 var hasPassword = localStorage.getItem('puppy_username_password') !== null; 26 var isGuestMode = localStorage.getItem('puppy_guest_mode') === 'true'; 27 return !!(username && hasPassword && !isGuestMode); 28 } 29 30 function loggedInTemplate() { 31 return ( 32 '<div class="citrus-account-window__scroll">' + 33 '<header class="citrus-account-window__header">' + 34 '<i class="fa-solid fa-user-gear citrus-account-window__header-icon" aria-hidden="true"></i>' + 35 '<h2 class="citrus-account-window__title">Account Settings</h2>' + 36 '</header>' + 37 '<div data-account-id="account-info" class="citrus-account-window__body">' + 38 '<section class="citrus-account-section">' + 39 '<h3 class="citrus-account-section__title">Account Information</h3>' + 40 '<div class="citrus-account-row"><span class="citrus-account-label">Username</span><span data-account-id="account-username" class="citrus-account-value"></span></div>' + 41 '<div class="citrus-account-row"><span class="citrus-account-label">Status</span><span class="citrus-account-value citrus-account-value--ok"><i class="fa-solid fa-lock" aria-hidden="true"></i> Password protected</span></div>' + 42 '</section>' + 43 '<section class="citrus-account-section">' + 44 '<h3 class="citrus-account-section__title">Status / Bio</h3>' + 45 '<p class="citrus-account-hint">A short line other users can see on Who\'s Here and in chat.</p>' + 46 '<label class="citrus-account-field"><input type="text" data-account-id="account-status-bio" class="citrus-account-input" maxlength="200" placeholder="e.g. vibing with the fish"></label>' + 47 '<p class="citrus-account-char"><span data-account-id="account-status-count">0</span>/200</p>' + 48 '<p data-account-id="account-status-error" class="citrus-account-msg citrus-account-msg--error" style="display: none;"></p>' + 49 '<button type="button" data-account-id="account-status-save" class="citrus-account-btn citrus-account-btn--primary">Save Status</button>' + 50 '</section>' + 51 '<section class="citrus-account-section">' + 52 '<h3 class="citrus-account-section__title">Change Password</h3>' + 53 '<label class="citrus-account-field"><span class="citrus-account-label">Current password</span><input type="password" data-account-id="current-password" class="citrus-account-input" autocomplete="current-password"></label>' + 54 '<label class="citrus-account-field"><span class="citrus-account-label">New password</span><input type="password" data-account-id="new-password" class="citrus-account-input" autocomplete="new-password"></label>' + 55 '<label class="citrus-account-field"><span class="citrus-account-label">Confirm new password</span><input type="password" data-account-id="confirm-password" class="citrus-account-input" autocomplete="new-password"></label>' + 56 '<p data-account-id="password-error" class="citrus-account-msg citrus-account-msg--error" style="display: none;"></p>' + 57 '<button type="button" data-account-id="change-password-btn" class="citrus-account-btn citrus-account-btn--primary">Change Password</button>' + 58 '</section>' + 59 '<section class="citrus-account-section">' + 60 '<h3 class="citrus-account-section__title">Your Statistics</h3>' + 61 '<div data-account-id="account-stats" class="citrus-account-stats"><div class="citrus-account-hint">Loading statistics...</div></div>' + 62 '</section>' + 63 '<section class="citrus-account-section citrus-account-section--logout">' + 64 '<button type="button" data-account-id="logout-btn" class="citrus-account-btn citrus-account-btn--danger">Log Out</button>' + 65 '</section>' + 66 '</div>' + 67 '<div data-account-id="not-logged-in" hidden></div>' + 68 '</div>' 69 ); 70 } 71 72 function authTemplate() { 73 return ( 74 '<div class="citrus-account-window__scroll">' + 75 '<header class="citrus-account-window__header">' + 76 '<i class="fa-solid fa-user citrus-account-window__header-icon" aria-hidden="true"></i>' + 77 '<h2 class="citrus-account-window__title">User Account</h2>' + 78 '</header>' + 79 '<div data-citrus-account-auth-root></div>' + 80 '</div>' 81 ); 82 } 83 84 function mountAuth(root) { 85 var authRoot = root.querySelector('[data-citrus-account-auth-root]'); 86 if (!authRoot || !global.__sharedUsername || typeof global.__sharedUsername.mountCitrusAuth !== 'function') { 87 return; 88 } 89 global.__sharedUsername.mountCitrusAuth(authRoot, { 90 onSuccess: function () { 91 renderContent(root); 92 } 93 }); 94 } 95 96 function renderContent(root) { 97 if (!root) return; 98 if (global.AccountPage && typeof global.AccountPage.destroy === 'function') { 99 global.AccountPage.destroy(); 100 } 101 root.innerHTML = isLoggedIn() ? loggedInTemplate() : authTemplate(); 102 if (isLoggedIn()) { 103 if (global.AccountPage && typeof global.AccountPage.init === 'function') { 104 global.AccountPage.init({ 105 root: root, 106 onLogout: function () { 107 renderContent(root); 108 } 109 }); 110 } 111 } else { 112 mountAuth(root); 113 } 114 } 115 116 function open() { 117 if (!global.CitrusDesktop || typeof global.CitrusDesktop.openAuxWindow !== 'function') { 118 if (global.__sharedUsername && typeof global.__sharedUsername.promptAgain === 'function') { 119 global.__sharedUsername.promptAgain(); 120 } else { 121 global.location.href = 'account.html'; 122 } 123 return; 124 } 125 126 ensureCss(); 127 128 var shellW = Math.min(460, Math.max(360, global.innerWidth - 48));
129 var shellH = Math.min(620, Math.max(420, global.innerHeight - 120)); 130 var posX = Math.max(16, Math.round((global.innerWidth - shellW) / 2)); 131 var posY = Math.max(16, Math.round((global.innerHeight - shellH - 72) / 2)); 132 133 var win = global.CitrusDesktop.openAuxWindow({ 134 id: ACCOUNT_AUX_ID, 135 label: 'Account', 136 icon: 'fa-solid fa-user', 137 w: shellW, 138 h: shellH, 139 x: posX, 140 y: posY, 141 nearChat: false, 142 onMount: function (contentEl) { 143 activeRoot = contentEl; 144 contentEl.className = 'citrus-account-window'; 145 renderContent(contentEl); 146 }, 147 onClose: function () { 148 if (global.AccountPage && typeof global.AccountPage.destroy === 'function') { 149 global.AccountPage.destroy(); 150 } 151 activeRoot = null; 152 } 153 }); 154 155 if (win && win.contentEl) { 156 activeRoot = win.contentEl; 157 activeRoot.className = 'citrus-account-window'; 158 renderContent(activeRoot); 159 } 160 } 161 162 function refresh() { 163 if (activeRoot) { 164 renderContent(activeRoot); 165 } 166 } 167 168 function isOpen() { 169 return !!activeRoot; 170 } 171 172 global.CitrusAccountWindow = { 173 open: open, 174 refresh: refresh, 175 isOpen: isOpen 176 }; 177 178 global.addEventListener('usernameSet', function () { 179 if (activeRoot) { 180 refresh(); 181 } 182 }); 183})(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.