1/** 2 * Chat Colour Scheme â ChatAsylum-parity (Features tab). 3 * 4 * Sets --cc-theme / --cc-theme-dark / --cc-theme-darker like Asylum's 5 * --ca-theme family, then accents chrome (borders, wording, tabs, modals). 6 * Backgrounds stay stock navy; chat messages + Type here stay navy. 7 * Cam ON / green status colours are left alone (same as Asylum). 8 * Preference is saved on the account (and cached in this browser). 9 */ 10(function () { 11 'use strict'; 12 13 var STORAGE_KEY = 'cc-chat-colour-scheme'; 14 var STYLE_ID = 'cc-user-colour-scheme-css'; 15 var ROOT_CLASS = 'cc-user-colour-scheme'; 16 17 /** Exact ChatAsylum CA_THEME_COLOURS palette. */ 18 var SWATCHES = [ 19 '#ff0000', '#ff1493', '#ff00ff', '#9b5cff', 20 '#0066ff', '#00ccff', '#00ff99', '#00ff00', 21 '#ffff00', '#ff9900', '#ffffff', '#999999' 22 ]; 23 24 var DEFAULTS = { 25 bg: '#0b0f17', 26 panel: '#141b2b', 27 panel2: '#182136', 28 border: '#243047', 29 accent: '#6aa7ff' 30 }; 31 32 /** ChatAsylum-style black surfaces used only while a colour scheme is active. */ 33 var THEMED = { 34 bg: '#000000', 35 panel: '#050505', 36 panel2: '#0a0a0a', 37 input: '#0a0a0a', 38 border: '#1a1a1a' 39 }; 40 41 function normalizeHex(hex) { 42 if (!hex || typeof hex !== 'string') return null; 43 var h = hex.trim().toLowerCase(); 44 if (h.charAt(0) !== '#') h = '#' + h; 45 if (/^#[0-9a-f]{3}$/.test(h)) { 46 h = '#' + h[1] + h[1] + h[2] + h[2] + h[3] + h[3]; 47 } 48 if (!/^#[0-9a-f]{6}$/.test(h)) return null; 49 return h; 50 } 51 52 /** Same as ChatAsylum caDarkenHex â amount multiplies each channel. */ 53 function darkenHex(hex, amount) { 54 hex = String(normalizeHex(hex) || '#ff0000').replace('#', ''); 55 var r = Math.max(0, Math.floor(parseInt(hex.slice(0, 2), 16) * amount)); 56 var g = Math.max(0, Math.floor(parseInt(hex.slice(2, 4), 16) * amount)); 57 var b = Math.max(0, Math.floor(parseInt(hex.slice(4, 6), 16) * amount)); 58 return '#' + [r, g, b].map(function (v) { 59 var s = v.toString(16); 60 return s.length === 1 ? '0' + s : s; 61 }).join(''); 62 } 63 64 function hexLuminance(hex) { 65 hex = String(normalizeHex(hex) || '#ff0000').replace('#', ''); 66 var r = parseInt(hex.slice(0, 2), 16) / 255; 67 var g = parseInt(hex.slice(2, 4), 16) / 255;
68 var b = parseInt(hex.slice(4, 6), 16) / 255; 69 function lin(c) { 70 return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4); 71 } 72 return 0.2126 * lin(r) + 0.7152 * lin(g) + 0.0722 * lin(b); 73 } 74 75 function loadSaved() { 76 try { 77 var raw = localStorage.getItem(STORAGE_KEY); 78 if (!raw) return null; 79 if (raw.charAt(0) === '#') return normalizeHex(raw); 80 var data = JSON.parse(raw); 81 if (!data) return null; 82 return normalizeHex(data.color || data.colour || ''); 83 } catch (_) { 84 return null; 85 } 86 } 87 88 function save(colour) { 89 try { 90 if (!colour) { 91 localStorage.removeItem(STORAGE_KEY); 92 return; 93 } 94 localStorage.setItem(STORAGE_KEY, JSON.stringify({ color: normalizeHex(colour) })); 95 } catch (_) {} 96 } 97 98 function ensureStyleEl() { 99 var el = document.getElementById(STYLE_ID); 100 if (el) return el; 101 el = document.createElement('style'); 102 el.id = STYLE_ID; 103 (document.head || document.documentElement).appendChild(el); 104 return el; 105 } 106 107 function clearRootVars(root) { 108 [ 109 '--accent', '--cc-theme', '--cc-theme-dark', '--cc-theme-darker', '--cc-theme-ink', 110 '--cc-scheme-vivid', '--cc-scheme-accent', '--bg', '--panel', '--panel2', '--border' 111 ].forEach(function (k) { 112 root.style.removeProperty(k); 113 }); 114 root.removeAttribute('data-cc-theme'); 115 } 116 117 function buildThemeCss() { 118 var t = 'var(--cc-theme)'; 119 var td = 'var(--cc-theme-dark)'; 120 var tdr = 'var(--cc-theme-darker)'; 121 var ink = 'var(--cc-theme-ink)'; 122 var black = THEMED.bg; 123 var panel = THEMED.panel; 124 var panel2 = THEMED.panel2; 125 var input = THEMED.input; 126 var R = 'html.' + ROOT_CLASS + ' '; 127 128 return [ 129 /* Themed = black surfaces (default without scheme stays navy). */ 130 R + '{--bg:' + black + ';--panel:' + panel + ';--panel2:' + panel2 + '}', 131 R + 'body,' + R + '.scale-fit-viewport,' + R + '.app{background:' + black + '!important}', 132 /* Chrome panels only â never paint cam tiles / .dock-head (blacks out video). */ 133 R + '.panel:not(.webcam-dock):not(.screen-dock),' + R + 'main.panel,', 134 R + 'aside.panel,' + R + '#leftCamDock,' + R + '.top,', 135 R + '.chat-head,' + R + '.composer,', 136 R + '#users,' + R + '.pop{background:' + panel + '!important}', 137 /* Scroll host behind cams â transparent so tile video is never covered by a fill */ 138 R + '#webcamPanel.dock-scroll,' + R + '#webcamPanel{background:transparent!important}', 139 R + '#chat,' + R + '.chat-scroll,' + R + '#ccRoomChatHost{background:' + black + '!important}', 140 R + 'textarea#msg{background:' + input + '!important;color:#e7ecf3!important;border-color:' + t + '!important}', 141 142 /* Shell borders */ 143 R + '.panel,' + R + 'main.panel,' + R + 'aside.panel,', 144 R + '#leftCamDock,' + R + '#webcamPanel,' + R + '.chat-head,', 145 R + '.composer,' + R + '.top,' + R + '.pop,', 146 R + '#feat,' + R + '#staff,' + R + '#shekelsModal{border-color:' + t + '!important}', 147 148 /* 149 * Cams â ChatAsylum caThemeLiveLast model ONLY: 150 * .dockCam / .webcam-dock / .mini-cam-slot { border-color: theme } 151 * Do NOT restyle video / dock-head layout (chat.html overlay CSS owns that). 152 * Only keep heads/titles fill-free so --panel black never paints over the stream. 153 */ 154 R + '.webcam-dock,' + R + '.screen-dock,' + R + '.mini-cam-slot{', 155 'border-color:' + t + '!important}', 156 R + '#webcamPanel .webcam-dock .dock-head,' + R + '.mini-cam-dock .webcam-dock .dock-head,', 157 R + '#webcamPanel .screen-dock .dock-head,' + R + '.mini-cam-dock .screen-dock .dock-head,', 158 R + '#webcamPanel .webcam-dock.is-featured .dock-head,', 159 R + '.mini-cam-dock .webcam-dock.is-featured .dock-head{', 160 'background:transparent!important;background-color:transparent!important;', 161 'background-image:none!important}', 162 R + '#webcamPanel .webcam-dock .dock-title,' + R + '.mini-cam-dock .webcam-dock .dock-title,', 163 R + '#webcamPanel .screen-dock .dock-title,' + R + '.mini-cam-dock .screen-dock .dock-title{', 164 'background:transparent!important;background-color:transparent!important;', 165 'color:' + t + '!important}', 166 167 /* Header buttons â Asylum solid theme fill (keep Visible/DMs status greens) */ 168 R + '.top .head-nav-btn:not(#adminPanel):not(#dndToggle):not(.is-on),', 169 R + '.top #headerShekelsBtn,' + 170 R + '.top #headerCasinoBtn,' + R + '.top #headerRaffleBtn,', 171 R + '.top #myProfileBtn,' + R + '.top #chatInboxBtn{', 172 'background:' + t + '!important;background-image:none!important;', 173 'border-color:' + t + '!important;color:' + ink + '!important}', 174 R + '.top #adminPanel{', 175 'background:' + t + '!important;border-color:' + t + '!important;color:' + ink + '!important}', 176 R + '.head-profile-menu,' + R + '.head-profile-btn,', 177 R + '.head-profile-item{border-color:' + t + '!important}', 178 R + '.head-profile-item:hover{color:' + t + '!important;border-color:' + t + '!important}', 179 180 /* Chat-head: Asylum fills Lobby / Polls / LIVE GAME / PING / Mini Docks / Mics */ 181 R + '.chat-head .head-nav-btn,', 182 R + '.chat-head .chat-head-btn,', 183 R + '.chat-head #pollsBtn,' + R + '.chat-head #ccHeadRoomsBtn,' + R + '.chat-head #chatHeadRoomName,', 184 R + '.chat-head .cc-room-tab.is-lobby,' + R + '.chat-head .cc-room-tab.is-lobby.is-active,', 185 R + '.chat-head #chatHeadRoomName.is-active,' + R + '.chat-head #chatHeadRoomName:hover,', 186 R + '.chat-head .cc-room-tab.is-lobby:hover,', 187 R + '.chat-head #chatHeadWatchParty,' + R + '.chat-head .cc-room-tab.is-watch-party,', 188 R + '.chat-head #ccOpenChallenges,' + R + '.chat-head #serverPingBtn,', 189 R + '.chat-head #miniCamDockToggle,' + R + '.chat-head #openMicGlobalToggle,', 190 R + '.chat-head #ccVolumeMixerToggle{', 191 'background:' + t + '!important;background-image:none!important;', 192 'border-color:' + t + '!important;color:' + ink + '!important;', 193 'box-shadow:0 4px 14px color-mix(in srgb,' + t + ' 35%,transparent)!important;filter:none!important}', 194 195 /* Composer toolbar â black fill, theme border/text/icons (Asylum) */ 196 R + '.composer .toolbar button,' + R + '.composer .toolbar select,', 197 R + '.composer .toolbar label,' + R + '#freezeScrollBtn:not(.is-frozen),', 198 R + '.composer button#send{', 199 'background:' + panel2 + '!important;color:' + t + '!important;border-color:' + t + '!important}', 200 R + '.composer .toolbar button svg,' + R + '.composer .toolbar button svg *,', 201 R + '.composer .toolbar .emojiBtn button,' + R + '.chat-head button svg,', 202 R + '.chat-head button svg *{', 203 'color:' + t + '!important;fill:' + t + '!important;stroke:' + t + '!important}', 204 205 /* Left dock shell black; #webcamPanel stays transparent (cams above). */ 206 R + '#leftCamDock{background:' + black + '!important}', 207 R + '#ccDiscover{background:' + black + '!important;border-top-color:' + t + '!important}', 208 R + '#ccDiscover .ca-cb-disc-hd,' + R + '#ccDiscover .ca-cb-disc-row,' + R + '#ccDiscover .ca-cb-disc-name,' + 209 R + '#ccDiscover .cc-disc-toolbar-label{', 210 'color:' + t + '!important}', 211 R + '#ccDiscover .ca-cb-disc-av{background:' + t + '!important;color:' + ink + '!important}', 212 R + '#ccDiscoverCollapseBtn{border-color:' + t + '!important;color:' + t + '!important}', 213 R + '#ccDiscover .ca-cb-viewers{background:#2a2e33!important;color:#c5c8ce!important}', 214 R + '#ccDiscoverRoomsBtn,.cc-disc-rooms-btn{', 215 'border-color:' + t + '!important;color:' + t + '!important;background:' + black + '!important}', 216 R + 'html.cc-watch-party #caWatchPartyLeave,html.cc-watch-party .ca-wp-leave{', 217 'background:' + t + '!important;border-color:' + t + '!important}', 218 R + '.cc-room-picker.is-asylum{border-color:' + t + '!important}', 219 R + '.cc-room-picker.is-asylum .cc-room-picker-hd,', 220 R + '.cc-room-picker.is-asylum .cc-room-picker-item,', 221 R + '.cc-room-picker.is-asylum .ca-cb-disc-name{color:' + t + '!important}', 222 R + '.cc-room-picker.is-asylum .ca-cb-disc-av{background:' + t + '!important;color:' + ink + '!important}', 223 R + '.cc-room-picker.is-asylum .cc-room-lock,' + R + '#ccDiscover .cc-room-lock{color:' + t + '!important}', 224 R + '.cc-room-picker.is-asylum .cc-wp-lock-toggle,' + R + '#ccDiscover .cc-wp-lock-toggle{', 225 'border-color:' + t + '!important;color:' + t + '!important}', 226 R + '.cc-room-picker.is-asylum .cc-wp-lock-toggle.is-locked,' + R + '#ccDiscover .cc-wp-lock-toggle.is-locked{', 227 'border-color:#8a3d3d!important;color:#ffb4b4!important}', 228 R + 'html.cc-watch-party #caWpVolume,html.cc-watch-party #caWpPlayerVol{accent-color:' + t + '!important}', 229 R + 'html.cc-wp-iframe #caWatchPartyStage .cc-wp-iframe-vol{accent-color:' + t + '!important}', 230 R + '.cc-broadcast-bar:not(.is-live) .webcam-toggle-btn:not(.active),', 231 R + '.cc-broadcast-bar:not(.is-live) .screen-share-toggle-btn:not(.active),', 232 R + '.cc-broadcast-bar:not(.is-live) .open-mic-toggle-btn:not(.active),', 233 R + '.webcam-toggle-btn:not(.active):not(.cc-bcast-ico),' + R + '.screen-share-toggle-btn:not(.active):not(.cc-bcast-ico),', 234 R + '.open-mic-toggle-btn:not(.active):not(.cc-bcast-ico){', 235 'color:' + t + '!important;border-color:' + t + '!important;background:transparent!important}', 236 R + '.webcam-toggle-btn:not(.active) .webcam-toggle-icon,', 237 R + '.screen-share-toggle-btn:not(.active) .screen-share-icon,', 238 R + '.open-mic-toggle-btn:not(.active) .open-mic-icon,', 239 R + '.webcam-toggle-btn:not(.active) svg,' + R + '.webcam-toggle-btn:not(.active) svg *,', 240 R + '.screen-share-toggle-btn:not(.active) svg,' + R + '.screen-share-toggle-btn:not(.active) svg *,', 241 R + '.open-mic-toggle-btn:not(.active) svg,' + R + '.open-mic-toggle-btn:not(.active) svg *,', 242 R + '.webcam-toggle-btn:not(.active) .user-webcam-icon-cam,', 243 R + '.screen-share-toggle-btn:not(.active) .user-screen-icon-glyph,', 244 R + '.open-mic-toggle-btn:not(.active) .open-mic-icon-glyph,', 245 R + '.webcam-toggle-btn:not(.active) .webcam-status,', 246 R + '.screen-share-toggle-btn:not(.active) .screen-share-status,', 247 R + '.open-mic-toggle-btn:not(.active) .open-mic-status{', 248 'color:' + t + '!important;fill:' + t + '!important;stroke:' + t + '!important;', 249 'filter:none!important;opacity:1!important}', 250 251 /* Asylum: active cam/mic/screen keep green */ 252 R + '.webcam-toggle-btn.active:not(.cc-bcast-ico):not(.privacy-friends_only):not(.privacy-ppv),', 253 R + '.cc-broadcast-bar:not(.is-live) .webcam-toggle-btn.active:not(.privacy-friends_only):not(.privacy-ppv),', 254 R + '.screen-share-toggle-btn.active:not(.cc-bcast-ico),' + R + '.open-mic-toggle-btn.active:not(.cc-bcast-ico),', 255 R + '.cc-broadcast-bar:not(.is-live) .screen-share-toggle-btn.active,' + R + '.cc-broadcast-bar:not(.is-live) .open-mic-toggle-btn.active{', 256 'background:#1e7a38!important;border-color:#2ecc71!important;color:#fff!important}', 257 R + '.webcam-toggle-btn.active .webcam-status{color:#1e7a38!important;background:#fff!important}', 258 259 /* Stop / Cam Off / Lock / Settings â black fill, theme wording */ 260 R + '#webcamStopBtn,' + R + '#webcamStopBtn.danger,' + R + '.webcam-control-btn.danger,', 261 R + '#webcamCameraOffBtn,' + R + '#webcamAudioBtn,', 262 R + '#webcamLockToggleBtn,' + R + '#webcamSettingsBtn,', 263 R + '#leftCamDock .webcam-control-btn:not(.primary),', 264 R + '#webcamPanel .webcam-control-btn:not(.primary){', 265 'background:' + panel2 + '!important;background-image:none!important;', 266 'color:' + t + '!important;border:1px solid ' + t + '!important}', 267 268 /* Non-cam dock chrome titles only (cam tile titles handled above) */ 269 R + '#leftCamDock > .dock-head,' + R + '#webcamPanel > .dock-head,', 270 R + '#leftCamDock .dock-title:not(.webcam-dock .dock-title){', 271 'color:' + t + '!important;border-color:' + t + '!important}', 272 273 /* User list filters â Asylum solid theme when active */ 274 R + '#usersTab,' + R + '#friendsTab,' + R + '.cc-userlist-tab,', 275 R + '.cc-userlist-tabs,' + R + '.cc-userlist-filters{color:' + t + '!important;border-color:' + t + '!important}', 276 R + '.cc-userlist-tab.is-active,' + R + '#usersTab.is-active,' + R + '#friendsTab.is-active{', 277 'border-bottom-color:' + t + '!important;color:' + t + '!important}', 278 R + '.cc-userlist-filter,' + R + '#users .filter-btn{', 279 'border-color:' + t + '!important}', 280 R + '.cc-userlist-filter:not(.is-active):not(.cc-filter-male):not(.cc-filter-female),', 281 R + '#users .filter-btn:not(.active):not(.is-active){color:' + t + '!important;background:' + panel2 + '!important}', 282 R + '.cc-userlist-filter.is-active,' + R + '#users .filter-btn.active,', 283 R + '#users .filter-btn.is-active{', 284 'background:' + t + '!important;border-color:' + t + '!important;color:' + ink + '!important}', 285 286 /* Shekels / wallet tabs */ 287 R + '.shek-nav-tabs .cc-shek-btn,' + R + '#shekelsModal .cc-shek-btn,' + R + '.cc-shek-btn{', 288 'background:' + panel2 + '!important;', 289 'border:1px solid ' + t + '!important;color:' + t + '!important;box-shadow:none!important}', 290 R + '.shek-nav-tabs .cc-shek-btn:hover,' + R + '.cc-shek-btn:hover,', 291 R + '.shek-nav-tabs .cc-shek-btn.active,' + R + '.cc-shek-btn.active{', 292 'background:' + t + '!important;border-color:' + t + '!important;color:' + ink + '!important;', 293 'box-shadow:0 4px 14px color-mix(in srgb,' + t + ' 28%,transparent)!important}', 294 /* Full-screen dimmer stays translucent (default rgba);
294 only the inner card is solid. */ 295 R + '#shekelsModal{background:rgba(0,0,0,.6)!important}', 296 R + '#shekelsModal > div{background:' + panel + '!important;border-color:' + t + '!important}', 297 R + '#shekelsModal .hd,' + R + '#shekelsModal h3,' + R + '#shekelsModal .shek-title{', 298 'color:' + t + '!important;border-color:' + t + '!important}', 299 300 /* Private messages â floating .dm window and the docked DM view in chat */ 301 R + '.dm,' + R + '.dm[data-dm-v2]{', 302 'background:' + panel + '!important;border-color:' + t + '!important}', 303 R + '.dm .dmHead,' + R + '.dm[data-dm-v2] .dmHead,' + R + '.dmTabs{', 304 'background:' + panel2 + '!important;background-image:none!important;', 305 'border-color:' + t + '!important;color:' + t + '!important}', 306 R + '#dmViews,' + R + '.dm .dmBody,', 307 R + '.dm .dmMsgs,' + R + '.dm[data-dm-v2] .dmMsgs,', 308 R + '#ccRoomChatHost > .dmBody.cc-docked-dm-view,', 309 R + '#ccRoomChatHost > .dmBody.cc-docked-dm-view .dmMsgs{', 310 'background:' + black + '!important}', 311 R + '.dm .dmCompose,' + R + '.dm[data-dm-v2] .dmCompose,', 312 R + '#ccRoomChatHost > .dmBody.cc-docked-dm-view .dmCompose{', 313 'background:' + panel + '!important;border-color:' + t + '!important}', 314 R + '.dmCompose input{', 315 'background:' + input + '!important;color:#e7ecf3!important;border-color:' + t + '!important}', 316 R + '.dmCompose .dm-tool-btn,' + R + '.dmCompose .dm-send-btn,' + R + '.dmCompose button{', 317 'background:' + panel2 + '!important;background-image:none!important;', 318 'color:' + t + '!important;border-color:' + t + '!important}', 319 R + '.dmCompose .dm-tool-btn:hover,' + R + '.dmCompose button:hover{', 320 'background:' + t + '!important;color:' + ink + '!important;border-color:' + t + '!important}', 321 R + '.dmTab{background:' + panel2 + '!important;border-color:' + t + '!important;color:' + t + '!important}', 322 R + '.dmTab.active{outline-color:' + t + '!important}', 323 R + '.dmTab.unread{background:' + td + '!important;border-color:' + t + '!important}', 324 R + '.cc-dm-head-tab.is-active{', 325 'background:' + t + '!important;border-color:' + t + '!important;color:' + ink + '!important}', 326 R + '.dmClose,' + R + '.dmMinBtn,' + R + '.dmResize{color:' + t + '!important}', 327 328 /* Features / Staff buttons */ 329 R + '#feat,' + R + '#staff{background:' + panel + '!important;border-color:' + t + '!important}', 330 R + '.cc-feature-btn,' + R + '#feat .cc-feature-btn,' + R + '#staff .cc-feature-btn{', 331 'background:' + panel2 + '!important;border:1px solid ' + t + '!important;color:' + t + '!important}', 332 R + '.cc-feature-btn:hover,' + R + '#feat .cc-feature-btn:hover,' + R + '#staff .cc-feature-btn:hover{', 333 'background:' + t + '!important;color:' + ink + '!important;border-color:' + t + '!important}', 334 R + '#feat .hd,' + R + '#staff .hd,' + R + '#feat h4,' + R + '#staff h4{', 335 'color:' + t + '!important;border-color:' + t + '!important}', 336 337 /* YouTube / soundboard / pop modals */ 338 R + '#youtubeModal,' + R + '#youtubeModal .ytBox,' + R + '#youtubeModal .ytHeader,', 339 R + '#youtubeModal input,' + R + '#youtubeModal .ytTab,' + R + '#youtubeModal button{', 340 'border-color:' + t + '!important}', 341 R + '#youtubeModal .ytHeader{color:' + t + '!important}', 342 R + '#youtubeModal .ytTab.active,' + R + '#youtubeModal .ytSearchBtn{', 343 'background:' + td + '!important;border-color:' + t + '!important;color:' + ink + '!important}', 344 R + '.cc-sb-panel,' + R + '.cc-sb-search,' + R + '.cc-sb-mutebtn,' + R + '.cc-sb-titlebar,', 345 R + '.cc-sb-head{border-color:' + td + '!important}', 346 R + '.cc-sb-vol{accent-color:' + t + '!important}', 347 R + '.pop .hd{border-color:' + t + '!important;color:' + t + '!important}', 348 R + '.pop .tabs button,' + R + '.pop .tab{color:' + t + '!important;border-color:' + t + '!important}', 349 R + '.pop .tabs button.active,' + R + '.pop .tab.active{', 350 'background:' + t + '!important;color:' + ink + '!important;border-color:' + t + '!important}', 351 352 /* Ticker / welcome bar accents */ 353 R + '.ticker,' + R + '.top .ticker{border-color:' + t + '!important}', 354 355 /* Colour scheme picker itself */ 356 R + '.cc-colour-scheme-box{border-color:' + t + '!important;background:' + panel + '!important}', 357 R + '.cc-colour-scheme-title{color:' + t + '!important}', 358 R + '.cc-colour-scheme-preview,' + R + '.cc-colour-scheme-apply,', 359 R + '#cc-colour-apply{background:' + t + '!important;border-color:' + t + '!important;color:' + ink + '!important}', 360 R + '.cc-colour-scheme-grid button,' + R + '#cc-colour-custom{border-color:' + t + '!important}', 361 R + '.cc-colour-swatch.is-selected{outline:2px solid #fff;outline-offset:1px;box-shadow:0 0 0 2px ' + t + '}', 362 R + '#cc-colour-reset{background:' + tdr + '!important;border-color:' + t + '!important;color:#fff!important}' 363 ].join('\n'); 364 } 365 366 function applyPalette(colour) { 367 var root = document.documentElement; 368 var hex = normalizeHex(colour); 369 if (!hex) { 370 root.classList.remove(ROOT_CLASS); 371 clearRootVars(root); 372 var stale = document.getElementById(STYLE_ID); 373 if (stale) stale.textContent = ''; 374 return null; 375 } 376 377 var dark = darkenHex(hex, 0.70); 378 var darker = darkenHex(hex, 0.45); 379 var ink = hexLuminance(hex) > 0.55 ? '#111111' : '#ffffff'; 380 381 root.classList.add(ROOT_CLASS); 382 root.setAttribute('data-cc-theme', hex); 383 // Themed chrome = black (Asylum). Asylum also sets --border/--accent to the 384 // pick; cam tiles only use border-color (see buildThemeCss) â never fills. 385 root.style.setProperty('--bg', THEMED.bg); 386 root.style.setProperty('--panel', THEMED.panel); 387 root.style.setProperty('--panel2', THEMED.panel2); 388 root.style.setProperty('--border', hex); 389 root.style.setProperty('--accent', hex); 390 root.style.setProperty('--cc-theme', hex); 391 root.style.setProperty('--cc-theme-dark', dark); 392 root.style.setProperty('--cc-theme-darker', darker); 393 root.style.setProperty('--cc-theme-ink', ink); 394 root.style.setProperty('--cc-scheme-vivid', hex); 395 root.style.setProperty('--cc-scheme-accent', hex); 396 397 ensureStyleEl().textContent = buildThemeCss();
398 return { color: hex, dark: dark, darker: darker, ink: ink }; 399 } 400 401 function applySaved() { 402 var saved = loadSaved(); 403 if (!saved) { 404 applyPalette(null); 405 return null; 406 } 407 return applyPalette(saved); 408 } 409 410 function persistAccount() { 411 if (window._ccChatAppearanceApplying) return; 412 if (typeof window.ccPersistChatAppearance === 'function') { 413 window.ccPersistChatAppearance(true); 414 } 415 } 416 417 function applyFromAccount(colour) { 418 var hex = normalizeHex(colour); 419 if (!hex) { 420 save(null); 421 applyPalette(null); 422 return null; 423 } 424 save(hex); 425 return applyPalette(hex); 426 } 427 428 function applyChoice(colour) { 429 var hex = normalizeHex(colour); 430 if (!hex) { 431 save(null); 432 applyPalette(null); 433 persistAccount(); 434 return null; 435 } 436 save(hex); 437 var applied = applyPalette(hex); 438 persistAccount(); 439 return applied; 440 } 441 442 function reset() { 443 save(null); 444 applyPalette(null); 445 persistAccount(); 446 return null; 447 } 448 449 function escAttr(s) { 450 return String(s == null ? '' : s) 451 .replace(/&/g, '&') 452 .replace(/"/g, '"') 453 .replace(/</g, '<'); 454 } 455 456 function featuresSectionHtml() { 457 var saved = loadSaved() || ''; 458 var swatchHtml = SWATCHES.map(function (c) { 459 var sel = saved && saved === c ? ' is-selected' : ''; 460 var border = c === '#ffffff' ? 'border:2px solid #666;' : 'border:2px solid rgba(255,255,255,.2);'; 461 return '<button type="button" class="cc-colour-swatch' + sel + '" data-cc-colour="' + escAttr(c) + '" title="' + escAttr(c) + '" style="width:100%;aspect-ratio:1;min-height:28px;border-radius:6px;padding:0;cursor:pointer;background:' + escAttr(c) + ';' + border + '"></button>'; 462 }).join(''); 463 464 return [ 465 '<div class="cc-colour-scheme-box" style="margin-top:14px;padding:12px;border:1px solid #26324b;border-radius:12px;background:#0c121e">', 466 ' <div class="cc-colour-scheme-title" style="display:flex;align-items:center;gap:8px;margin-bottom:10px;font-weight:700;font-size:14px">', 467 ' <span aria-hidden="true">ð¨</span> Chat Colour Scheme', 468 ' </div>', 469 ' <p class="muted" style="margin:0 0 10px;font-size:12px">Same colours as ChatAsylum. Default is navy; when you pick a colour, backgrounds go black and icons/tabs match that colour. Saved to your account.</p>', 470 ' <div class="cc-colour-scheme-grid cc-colour-swatch-grid" style="display:grid;grid-template-columns:repeat(8,minmax(28px,1fr));gap:8px;margin-bottom:10px">' + swatchHtml + '</div>', 471 ' <div class="cc-colour-scheme-preview" style="height:28px;border-radius:6px;background:' + escAttr(saved || '#182136') + ';margin-bottom:10px;border:1px solid rgba(255,255,255,.12)"></div>', 472 ' <input type="color" id="cc-colour-custom" value="' + escAttr(saved || '#ff0000') + '" title="Custom colour" style="width:100%;height:38px;background:#000;border:1px solid #26324b;border-radius:6px;padding:2px;margin:0 0 8px;cursor:pointer">', 473 ' <button type="button" id="cc-colour-apply" class="cc-feature-btn cc-colour-scheme-apply" style="width:100%;margin-top:8px;padding:10px">Apply Chat Colour</button>', 474 ' <button type="button" id="cc-colour-reset" class="cc-feature-btn cc-colour-scheme-reset" style="width:100%;margin-top:8px;padding:10px;background:#1a2233">Reset Colour Scheme</button>', 475 '</div>' 476 ].join('\n'); 477 } 478 479 function wireFeatures(root) { 480 root = root || document; 481 var box = root.querySelector ? root.querySelector('.cc-colour-scheme-box') : null; 482 if (!box || box._ccColourWired) return; 483 box._ccColourWired = true; 484 485 var selected = loadSaved() || ''; 486 var preview = box.querySelector('.cc-colour-scheme-preview'); 487 var custom = box.querySelector('#cc-colour-custom'); 488 489 function setSelected(hex) { 490 selected = normalizeHex(hex) || ''; 491 box.querySelectorAll('.cc-colour-swatch').forEach(function (btn) { 492 btn.classList.toggle('is-selected', !!selected && btn.getAttribute('data-cc-colour') === selected); 493 }); 494 if (preview) preview.style.background = selected || '#182136'; 495 if (custom && selected) custom.value = selected; 496 } 497 498 box.querySelectorAll('.cc-colour-swatch').forEach(function (btn) { 499 btn.addEventListener('click', function () { 500 setSelected(btn.getAttribute('data-cc-colour')); 501 // Asylum applies on swatch click immediately. 502 applyChoice(selected); 503 if (typeof showToast === 'function') showToast('Chat colour applied', 'success'); 504 }); 505 }); 506 507 if (custom) { 508 custom.addEventListener('input', function () { 509 if (preview) preview.style.background = custom.value; 510 setSelected(custom.value); 511 }); 512 } 513 514 var applyBtn = box.querySelector('#cc-colour-apply');
515 if (applyBtn) { 516 applyBtn.addEventListener('click', function () { 517 var pick = selected || (custom && custom.value) || ''; 518 if (!normalizeHex(pick)) { 519 if (typeof showToast === 'function') showToast('Pick a colour first', 'info'); 520 return; 521 } 522 applyChoice(pick); 523 if (typeof showToast === 'function') showToast('Chat colour applied', 'success'); 524 }); 525 } 526 527 var resetBtn = box.querySelector('#cc-colour-reset'); 528 if (resetBtn) { 529 resetBtn.addEventListener('click', function () { 530 selected = ''; 531 setSelected(''); 532 if (custom) custom.value = '#ff0000'; 533 reset(); 534 if (typeof showToast === 'function') showToast('Colour scheme reset to default', 'success'); 535 }); 536 } 537 } 538 539 function applyAccountColourIfReady() { 540 try { 541 var u = window.me; 542 if (!u || !u.chat_appearance || !u.chat_appearance.saved) return false; 543 if (u.chat_appearance.colour_scheme === undefined || u.chat_appearance.colour_scheme === null) return false; 544 applyFromAccount(u.chat_appearance.colour_scheme); 545 return true; 546 } catch (_) { 547 return false; 548 } 549 } 550 551 window.ccChatColourScheme = { 552 SWATCHES: SWATCHES, 553 loadSaved: function () { 554 var c = loadSaved(); 555 return c ? { color: c, intensity: 85 } : null; 556 }, 557 applySaved: applySaved, 558 applyChoice: function (color) { return applyChoice(color); }, 559 applyFromAccount: applyFromAccount, 560 reset: reset, 561 featuresSectionHtml: featuresSectionHtml, 562 wireFeatures: wireFeatures 563 }; 564 565 try { 566 if (!applyAccountColourIfReady()) applySaved(); 567 } catch (_) {} 568 if (document.readyState === 'loading') { 569 document.addEventListener('DOMContentLoaded', function () { 570 try { 571 if (!applyAccountColourIfReady()) applySaved(); 572 } catch (_) {} 573 }); 574 } 575})();
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.