PageSourceSearch

https://chatcrack.us/chat-colour-scheme.js?v=20260923-bcast2

js chatcrack.us collected 2026-09-24 15:14:14 UTC 29,104 bytes, 575 lines download raw bytes

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, '&amp;')
452      .replace(/"/g, '&quot;')
453      .replace(/</g, '&lt;');
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.