From 49827d4c6cb3c34bd83f1a00a3a2340e85f61576 Mon Sep 17 00:00:00 2001 From: acidvegas Date: Sat, 14 Feb 2026 02:10:02 -0500 Subject: [PATCH] Initial commit --- favicon.ico | Bin 0 -> 20207 bytes index.html | 40 ++ logo.png | Bin 0 -> 17001 bytes script.js | 1157 +++++++++++++++++++++++++++++++++++++++++++++++++++ style.css | 400 ++++++++++++++++++ 5 files changed, 1597 insertions(+) create mode 100644 favicon.ico create mode 100644 index.html create mode 100644 logo.png create mode 100644 script.js create mode 100644 style.css diff --git a/favicon.ico b/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..ad0892d9823d6d18790a9ed08d6e39d68c8dcbd1 GIT binary patch literal 20207 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 literal 0 HcmV?d00001 diff --git a/index.html b/index.html new file mode 100644 index 0000000..ed06e32 --- /dev/null +++ b/index.html @@ -0,0 +1,40 @@ + + + + + + SuperNETs IRC + + + + +
+
+ +
SuperNETs IRC
+ + +
+
+
+ + + diff --git a/logo.png b/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..a5ca347d3c3fde3944af009a122701f40e117bbd GIT binary patch literal 17001 zcmeHP3pmti+n*VOnJ~toh)JVyD6M145EAXuyUE< z_(O?I-}l*5Yt^Ss+o$~QYq@9KR@$nyjP8Li*N3Sr9G*D(FRnAO+&!(ah(zb$2&4b1 zR1s(rNB=<0Mu|9KYFtD0w{0xu%5T=e6SEcQwEFvLs{dFUPb9_5{$^cM0xwX_SBde> zUN~XWw=<}z4ji_d-MSUOQV6|tn#s2_2=yc?KR9UK-hWVxBk=5`M$d%S3sA!gjOFrn zDSUHS4!ic-86-Cbi*mPlr8Hqv={6TfjE^Uc@q!2EKl1`b(MtTp zqh~gYFI!ECPAg0EV9xs4Y-})bj2nLyZ0!EJwP}Y$U*loxJw!P>u_oc_k_JCl_=gS; ze2DGMb;S#(UeuxSGE^s3iM3ex0h}G44igX$;a0`y?n9LN?Y1iMV#$o!dGJ{Bx7WzzL2^MI%Gf9rd7IB)G zKJA{07uYbprD2O*V_VQUV~qL}A&TuPWH33^7p@5=D%uIXCFC!Nm#A9&pes5UH&=& z{_XE&%EHadb>%^f7;|2PFrTrjV-K*~i?mLun45aFCAl)+$2bu{=H^O$j9LKyt&TMn zCcO(dxT#>bOpqw%{hnh{r&JQn+G?^uoD?cFLF^J_rU`3*XY3`x z{UxFPC4msYRfAI-8gG*EdLUrz?I_#K)bIpgqVv=FA= zPm3+$KucwDuY4%r+5Nb4(cNorT~-gSmctKBS>yladRkrR2W3QFh6`w$ z8iH@iV^?$bryGmhF-N-@oEOPJcE;QWuDR!A@^Y)5m27hpc5ay4j&xY&!$GgaX zy@BY6Ki&5^@Six+8doI;EP2i@6(&$t3p5+A>AemZKdE8$^(QLk1Ga`~8|IkNzE}G> zPBbnnK3W8$YwR9us#1_1(v{S%eL*>P`OzDI@a2_JgQ6R!i{YzQe?yrn>kK&|_ z@U)aJ#)&BLO`NFk)j+e#)k^yJXJBJXPwB zuHD7yvUfKm=xJKL%Y6Or{8#W8M33v-hRft@H&-8?oOFNB?dxAO340(svOj5PsUw`E zEw7?>>6I>AVy^Sk#54VUN@lHz8gC^`(t@JOdCMN1H&f)A6KO`T*?Dzo-TeLWF0Zrq zYJwNxQsCh3!gP3e)%>u0?L3Y|yl(G`){Snll~e*H)y{}=L<=o$=WD(~5jgA69`qI_ z>9sWK^x?N9HrDyW@K;~z5?QeviPf=Bj~SD@rfy5EZx-YNDatDfw~~ZDw+9Y;e8k@j zcEEHak!uDkn>#y&JKnT&(?BYcELsyw<)57JnbU5{z*KMyPUv)-C78XNU7d-pJJm>$ z-|xNsbDPBe`b^%k_WSe61Rg_Q0(KK9q>KZ~RtTJ!ZGJY){k{jfjXl~SRTMGJl3>_r|e_k@Xx+I3Jb!nfL?k{!2CCpmiVQJnM2+42!^ zq4t)T3oHM@1u8#tg7)^hl<>2XctP?(F7F3rVZu#>c;n?-w4OXV%~{{2KUKG9z6|ha z`UsE6V+{B`kc=VVpG06Vdpy~&i&xaoxw^$hL-FBRDTvxz8;RQshlF;3(!uj>`NN4h z!{z})JEW?)>~Kw#`nf{wzB7f^b@5AQb7OvC#=r-Op?*Ys?>otBpZImhmGAUPEH zE5?6;3yR6+=)zvo!X}X%Hib@$yqf2=3*x6i7B~_V!G)T2Xg8SgDcmnd5z9(h-{$P7 z>T$U4pEEAn4(kJ=rekzgz9v2cL(Q(bP=oxFZ|AYWXA^x~+8W2`AKmL({?sfKl7%p% zQ}8>o-pWyhr0B)JT@e|%!&~vfQeMZA$qHyeQq7Fe$sYDVe28LqD02w^2HE^O$4z&j z@DDUs-H!Tj0!rU;(d4cbFXI9Q8tOB(#l3qY(o?1396l(XgadBAeQ>e^;sKrZQYC>4 z%&ts9t}318@pI+7dthvKg35gtDqArZwv0h`8Q3?g-OvRL2I{hE@ji#TqCvsIye1L4 zJ~V%nl~jKEs!BLs;_e-$8iR2djZ}W?_Xi$A(969?M*I@S1@1FLw+BnU?$l`;cviF4x`=yKf0<(}5A+m3a1L7q zb{v{+y;pp>w+yKgkZa`Xk}+j;|tYj1SBFPNa0Y#4YmFUY=6s`e~+sSqP%7-7CpCER zoEZxQY}E+ZM*#@*EqN-LEC|c_aMSt=mew0g(9hPjvUw=#stUQVMj1|WhdlUr``2Lu z>(-+$w=RF)X>0qoLPxo0tsGC3s0xh9|9Mh{BjCUn@_C`1x-{eFfa{&Mf)!*(Tb122 zfj=X;43-CqrFh!uY~IZFD03894CyK->h>4{MNo%{Iq7!HtAi0E2<@4lv zoaJB|;70D8DIAr45QiMYL-Pa*4OBDQWc9Xuuc$TqyYb}y`qQoY=dm$6S%SVP+_{xz zw-IA_dEK|w#Nw$?F>ibVaHSnCCxj{R?1*FJY9dOutKHv0&Vz~-g zi%`B8b!)AcG^43pZiT(#55QjG+x!#urX!74Mn)7{V0w?oSU#P~mstuIFS2zTB%;i8 zt3LtwL=+}GQ5tetS_-x<29tsVzxMeOJ^m_ z_qtxx7N|KSSEH^3Lt;vDF#TYOp(Sg-7%c#SF|dq*1p~{JLh3NTJE4%w2>El|Lm6@U{Dau`HT`<2oYceZhjm z%Wygd3TDM-S#h?ubpe82E`=_fs;K+lv$S;Dc?l=x?rI2^r#jRap@r~74L~!g<}FTf zYzyp72&J>f81<(|RN%}W?mdQ8=4Lc&pLJo& zLyE|!oz+t5(q+eC_jddedk|W}G<9me^4RO2c|s>ppzhNV1v!8@W#t8^#h|*qG@Z&% z^n*+$c)j5g)63-Hee+DCiZ`^>#HP~vl;F^=|Br`$zzZ@Ntp)g7GGOe`|2a?(TjJVi zkkFfS;?k}w-9;4!gR4JzC{j|%wAGthRt@ni-wW1mJWd&)45TntfAIiY{GikxZ!ww^pN!irIIMB*jnqY2Mi*&_}*t@=_A~W`dUW=VQo}gbgr8hEi4d@#9=- ztL$7oBEN&4;3Mxn<%Mq^b~wM&0yW}U^tN7yR-}Xoc)Qec-AY@HyHk9Z$-&5%-Kybx zaKh5qdjTNEZCh;wHnWACBbAy4H2LudlH%hJo%rMfJr7NuWN8QV0!rx&Hq z8e!1@aU_@Vp$!+H%@=<(IL^2m;Pd2_@=3t&R3 za^9DsQ3ge*+*1Pt6($_|9;K(uEu~tfu$Upj8L7bW(xpiw{7#Umv6Iwp7oN&GV|566 zirlv5#IrYd^k!}WCKyli{Uumwgh9U;oS12%DZfTRKOenqZ48&a3c!6PSYw0$d4)a( z$T2|vsQ|gN(K*8D-nE`$QzBopwV{}ZgcOS|F78rhk z0+Xo79d)-r)|fhV6Jjc_p$;aq<vxxfNBE*;a^Xh! zi%%|$rL&Dt2Zdjf^)(JBq$YP_FnE>}VV%UI1=XsbDO#hRz~Ad3$IBkWDtZHW#gx@?pJxz5=_d zN6oN;f<1)?e0PwCBUmG*R%O3&1?RN$tMPOQMn$Pcek6i4q}q6*)^NY4p`mg@VR2sCG#7XuY2bK^I-TDAUsK_BzO6U%hcVUjp$| zLDf}TlT#^=m|8Gx3<^}rAAy1d7f*DO@A&MocSwz@w{Wj!_v_T)t?AcM5Y;Dpen%)& z%61Lq%mL)WZ7^2O81j)4_Q#XpuT!(V7S987tCLt<_Y=AeI&D**&$g|Aw3gV{%_aGF ztzO<)NhfR}%vyH*crt0w8iA1H_(leMz)-kse(>=wh@2j6Sa%$XZQ*K-H$*-^3T6>P zolyCSiIq@I8;F~`P@KjmMae^l4b#@_%tZ@;#_`J=@}K}0HDzzek5mFD!iM>Cr)GwN zDnLz^6Esdt)2XhJZC`nL>q4xxuq1B_6y>4_s;F~I=nCvxnbk^Df&?4}Po0N7Qt%kP zR^mM7^JBN(f-r1;TYSkKB}Boz7nl7!5BvcL6-(mxIt1XL#20sZ8j=Y}BbQ4<2XN=; z!o|m7LHFmy4N$y`n(B)i%XWdvqSN|5B|RX?!nvearr=JZEb{$6F^4yA(mSsNlAY7s zU;)+6ux8-XPnXS%(Kk_xhKU+n7^w!AkS%@=Quv}F-ZsR2)fVt8EVeZJo6tL_%hKZ?*G2sOW&24#8_1Ig+J2hi%zHkfa-d!9qBz zTc&1pJKcXs&FbZ$k5L1k9v=z3eRrA~cYwHlXh8N3heZ@iQnmNse4;Si&X#HNF-MQ+ z4FH)wHByQoBZk(5^+f^(0Dfxq4JwfjnB=cLbA7W8COWm-B|u`6kGL(;EP!$upPHvB z)Ku}ipYfJG2X#?a6MSw3Cg>YxX7ZsCS^S7((Q&oOqS#6c&)k8RmB4v1v-wPHY?%`Y#g0SOL0~mn$ z*(Vube=fXh*juTE1PS8fZN4M2f+xnoI#u)2NMMB^+HaPG0hy~jtw@`}J+#>RJWU}L z^BkQY=y{;P7{c8toJu1QT2&V%C@nyAT|u5;k=r?GE*~sU&5wRF2q{SRAjo|gJStvP zRqzR9?SiMEpWW~n8&r^Z9diDKem@y}hNdm+90D*e8S(*^`mib_QQ-Zw3PgGN)Op~d zBD&zs9c4{&G>8@;z>}_o+#$JvK0>LJgJ&QSfT>wZ-t69IEPz3pJ_nNLm}!lo*kHf z*MqrC`&A!_&IV63%-1nbn)^yDPf!w<$3UU(V_0)-+lhD_VIekaW?}B ziLjU(P2dx8N|8tvCppRvGC0T_1}ZiQig+2gJ5+dJ4}hGHvFh0l^wcg$PJvIo%t5GD zfy$bU(5^fh9<#bBl@qeHX_7&4=*fR4?0=0l55yV>J|+vFWoMxrN1Vn8eF;VxjC(4v z*hpVEl{Etu>Zn3iM^_C5r@oEjaETy@Q8ZgR0aT@ zM)T`YB_EHWE5T!&QPBgHBFCfJx2H!Bz+X=M>&*&=0a#!o(v{3&sO=zj}-~ zt_Z|W#kVLN1nGFbyxb5G*>7hz9J2ACb$$0325t1TRbJEh1J5mXlbXHV&3!wT?W> zA?E0Sx_F|p2p#fQC!NZKv?#Ou`UF%8xE`MV*VpUCqRktD?svI^sN9zoU;rmRoc{!s zc{&1H9Sb7S890?Fv3lV@hJi`;{s}l7K^6Uf0sex_gbTt-xZZ 1000) { + nickColors.delete(nickColors.keys().next().value); + } + return c; + } + + // --- HTML escape --- + function esc(t) { + return t.replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); + } + + // --- Nick truncation --- + function truncNick(n) { + if (n.length > NICK_MAX) return n.substring(0, NICK_MAX) + '..'; + return n; + } + + // --- Chat nick column + separator --- + function chatNick(displayNick, color) { + let dn = displayNick; + if (dn.length > NICK_MAX) dn = dn.substring(0, NICK_MAX) + '..'; + return '' + esc(dn) + ' \u2502 '; + } + + // --- IRC formatting → HTML --- + function formatIRC(text) { + let out = ''; + let bold = false, italic = false, underline = false; + let fg = null, bg = null; + let i = 0; + let spanOpen = false; + + function applyStyle() { + if (spanOpen) { out += ''; spanOpen = false; } + const s = []; + if (bold) s.push('font-weight:bold'); + if (italic) s.push('font-style:italic'); + if (underline) s.push('text-decoration:underline'); + if (fg !== null && IRC_COLORS[fg]) s.push('color:' + IRC_COLORS[fg]); + if (bg !== null && IRC_COLORS[bg]) s.push('background-color:' + IRC_COLORS[bg]); + if (s.length) { + out += ''; + spanOpen = true; + } + } + + while (i < text.length) { + const c = text.charCodeAt(i); + + if (c === 0x02) { + bold = !bold; applyStyle(); i++; + } else if (c === 0x1D) { + italic = !italic; applyStyle(); i++; + } else if (c === 0x1F) { + underline = !underline; applyStyle(); i++; + } else if (c === 0x16) { + const tmp = fg; fg = bg; bg = tmp; applyStyle(); i++; + } else if (c === 0x0F) { + bold = italic = underline = false; + fg = bg = null; applyStyle(); i++; + } else if (c === 0x03) { + i++; + if (i < text.length && text[i] >= '0' && text[i] <= '9') { + let fs = text[i++]; + if (i < text.length && text[i] >= '0' && text[i] <= '9') fs += text[i++]; + fg = parseInt(fs, 10); + if (i < text.length && text[i] === ',') { + const ci = i + 1; + if (ci < text.length && text[ci] >= '0' && text[ci] <= '9') { + i = ci; + let bs = text[i++]; + if (i < text.length && text[i] >= '0' && text[i] <= '9') bs += text[i++]; + bg = parseInt(bs, 10); + } + } + } else { + fg = bg = null; + } + applyStyle(); + } else if (c === 0x04) { + i++; + if (i + 5 < text.length) { + const hex = text.substring(i, i + 6); + if (/^[0-9a-fA-F]{6}$/.test(hex)) { + if (spanOpen) { out += ''; spanOpen = false; } + const s = []; + if (bold) s.push('font-weight:bold'); + if (italic) s.push('font-style:italic'); + if (underline) s.push('text-decoration:underline'); + s.push('color:#' + hex); + if (bg !== null && IRC_COLORS[bg]) s.push('background-color:' + IRC_COLORS[bg]); + out += ''; + spanOpen = true; + i += 6; + continue; + } + } + } else { + if (text[i] === '&') out += '&'; + else if (text[i] === '<') out += '<'; + else if (text[i] === '>') out += '>'; + else out += text[i]; + i++; + } + } + + if (spanOpen) out += ''; + return out; + } + + function stripIRC(text) { + return text.replace(/\x03(\d{1,2}(,\d{1,2})?)?/g, '') + .replace(/\x04([0-9a-fA-F]{6})?/g, '') + .replace(/[\x02\x1D\x1F\x16\x0F]/g, ''); + } + + // ============================================================ + // Window / tab management + // ============================================================ + function createWindow(name) { + if (!windows[name]) { + windows[name] = { messages: [], nicks: [], unread: 0, mentioned: false }; + } + renderChannelList(); + } + + function switchWindow(name) { + if (!windows[name]) createWindow(name); + activeWindow = name; + windows[name].unread = 0; + windows[name].mentioned = false; + renderChannelList(); + renderMessages(); + updateNicklistVisibility(); + renderNickList(); + updateInputNick(); + updateTopicBar(); + updateStatusBar(); + inputEl.focus(); + } + + function addMessage(windowName, html, timestamp) { + if (!windows[windowName]) createWindow(windowName); + const t = timestamp ? new Date(timestamp) : new Date(); + const ts = ('0' + t.getHours()).slice(-2) + ':' + + ('0' + t.getMinutes()).slice(-2) + ':' + + ('0' + t.getSeconds()).slice(-2); + const line = '[' + ts + '] ' + html; + const win = windows[windowName]; + win.messages.push(line); + if (win.messages.length > 5000) win.messages.shift(); + + if (windowName === activeWindow) { + appendLine(line); + } else { + win.unread++; + renderChannelList(); + } + } + + // --- Rendering --- + function appendLine(html) { + const atBottom = messagesEl.scrollTop + messagesEl.clientHeight >= messagesEl.scrollHeight - 20; + const div = document.createElement('div'); + div.className = 'line'; + div.innerHTML = html; + messagesEl.appendChild(div); + while (messagesEl.children.length > 5000) messagesEl.removeChild(messagesEl.firstChild); + if (atBottom) messagesEl.scrollTop = messagesEl.scrollHeight; + } + + function renderMessages() { + messagesEl.innerHTML = ''; + const win = windows[activeWindow]; + if (!win) return; + const frag = document.createDocumentFragment(); + win.messages.forEach(function (m) { + const div = document.createElement('div'); + div.className = 'line'; + div.innerHTML = m; + frag.appendChild(div); + }); + messagesEl.appendChild(frag); + messagesEl.scrollTop = messagesEl.scrollHeight; + } + + function renderChannelList() { + channelsEl.innerHTML = ''; + for (const name in windows) { + const tab = document.createElement('div'); + const win = windows[name]; + let cls = 'tab'; + if (name === activeWindow) { + cls += ' active'; + } else if (win.mentioned) { + cls += ' mentioned'; + } else if (win.unread > 0) { + cls += ' unread'; + } + tab.className = cls; + tab.textContent = name; + tab.onclick = (function (n) { return function () { switchWindow(n); }; })(name); + channelsEl.appendChild(tab); + } + } + + function renderNickList() { + nicklistEl.innerHTML = ''; + const win = windows[activeWindow]; + if (!win || !win.nicks.length) return; + + const groups = { '~': [], '&': [], '@': [], '%': [], '+': [], '': [] }; + + win.nicks.forEach(function (n) { + const bare = n.replace(/^[~&@%+]+/, ''); + const pfx = n.slice(0, n.length - bare.length); + const topPfx = pfx ? pfx[0] : ''; + const key = groups[topPfx] !== undefined ? topPfx : ''; + groups[key].push({ bare: bare, pfx: pfx, sort: bare.toLowerCase() }); + }); + + const order = ['~', '&', '@', '%', '+', '']; + const frag = document.createDocumentFragment(); + + order.forEach(function (key) { + const list = groups[key]; + if (!list.length) return; + + list.sort(function (a, b) { return a.sort.localeCompare(b.sort); }); + + const header = document.createElement('div'); + header.className = 'nick-category'; + header.textContent = PREFIX_LABELS[key] + ' (' + list.length + ')'; + frag.appendChild(header); + + list.forEach(function (entry) { + const div = document.createElement('div'); + div.className = 'nick'; + const pfxColor = entry.pfx ? (PREFIX_COLORS[entry.pfx[0]] || '#666') : ''; + const pfxHtml = entry.pfx ? '' + esc(entry.pfx) + '' : ''; + const displayNick = truncNick(entry.bare); + div.innerHTML = pfxHtml + esc(displayNick); + div.title = entry.bare; + div.ondblclick = (function (b) { return function () { + if (!windows[b]) createWindow(b); + switchWindow(b); + }; })(entry.bare); + frag.appendChild(div); + }); + }); + + nicklistEl.appendChild(frag); + } + + // --- Panel visibility --- + function isChanWindow(name) { + return name && (name[0] === '#' || name[0] === '&'); + } + + function updateNicklistVisibility() { + if (showNicklist && isChanWindow(activeWindow)) { + nicklistEl.classList.remove('hidden'); + } else { + nicklistEl.classList.add('hidden'); + } + } + + function updateChanlistVisibility() { + if (showChanlist) { + channelsEl.classList.remove('hidden'); + } else { + channelsEl.classList.add('hidden'); + } + } + + // --- Get our prefix in a channel --- + function getOurPrefix(windowName) { + const win = windows[windowName]; + if (!win || !win.nicks.length) return ''; + for (let i = 0; i < win.nicks.length; i++) { + const bare = win.nicks[i].replace(/^[~&@%+]+/, ''); + if (bare === nick) { + return win.nicks[i].slice(0, win.nicks[i].length - bare.length); + } + } + return ''; + } + + function updateInputNick() { + const pfx = isChanWindow(activeWindow) ? getOurPrefix(activeWindow) : ''; + inputNickEl.textContent = pfx + nick + ':'; + inputNickEl.style.color = getNickColor(nick); + } + + function updateTopicBar() { + if (isChanWindow(activeWindow)) { + const win = windows[activeWindow]; + const count = win ? win.nicks.length : 0; + const mode = channelModes[activeWindow] || ''; + const modeStr = mode ? ' [+' + mode + ']' : ''; + const topic = channelTopics[activeWindow] || ''; + topicbarEl.innerHTML = + '' + esc(activeWindow) + '' + + '' + esc(modeStr) + ' (' + count + ') ' + + (topic ? formatIRC(topic) : ''); + topicbarEl.title = topic ? stripIRC(topic) : ''; + topicbarEl.classList.remove('hidden'); + } else { + topicbarEl.classList.add('hidden'); + topicbarEl.title = ''; + } + } + + function updateStatusBar() { + // Status bar now only has toggle buttons and the github link — nothing dynamic needed + } + + // ============================================================ + // IRC protocol + // ============================================================ + function send(data) { + if (ws && ws.readyState === WebSocket.OPEN) { + ws.send(data + '\r\n'); + } + } + + function parseMessage(raw) { + let tags = {}; + let i = 0; + + if (raw[0] === '@') { + const sp = raw.indexOf(' '); + raw.substring(1, sp).split(';').forEach(function (t) { + const eq = t.indexOf('='); + if (eq === -1) tags[t] = ''; + else tags[t.substring(0, eq)] = t.substring(eq + 1); + }); + i = sp + 1; + } + + while (i < raw.length && raw[i] === ' ') i++; + + let prefix = ''; + if (raw[i] === ':') { + const sp = raw.indexOf(' ', i); + prefix = raw.substring(i + 1, sp); + i = sp + 1; + } + + while (i < raw.length && raw[i] === ' ') i++; + + const rest = raw.substring(i); + const parts = []; + let j = 0; + while (j < rest.length) { + if (rest[j] === ':') { parts.push(rest.substring(j + 1)); break; } + const sp = rest.indexOf(' ', j); + if (sp === -1) { parts.push(rest.substring(j)); break; } + parts.push(rest.substring(j, sp)); + j = sp + 1; + while (j < rest.length && rest[j] === ' ') j++; + } + + const command = parts.length ? parts[0].toUpperCase() : ''; + const prms = parts.slice(1); + const m = prefix.match(/^([^!@]+)/); + const fromNick = m ? m[1] : prefix; + + return { tags: tags, prefix: prefix, fromNick: fromNick, command: command, params: prms }; + } + + // ============================================================ + // Message handler + // ============================================================ + function handleMessage(msg) { + const tags = msg.tags; + const fromNick = msg.fromNick; + const command = msg.command; + const p = msg.params; + const timestamp = tags['time'] || null; + const batchId = tags['batch'] || null; + + if (batchId && batches[batchId]) { + batches[batchId].messages.push(msg); + return; + } + + switch (command) { + + case 'PING': + send('PONG :' + (p[0] || '')); + break; + + case 'CAP': { + const sub = p[1]; + if (sub === 'LS') { + const isMulti = p[2] === '*'; + const capsStr = isMulti ? p[3] : p[2]; + if (capsStr) { + availableCaps = availableCaps.concat( + capsStr.split(' ').filter(Boolean).map(function (c) { return c.split('=')[0]; }) + ); + } + if (isMulti) break; + const desired = [ + 'server-time', 'batch', 'message-tags', + 'draft/chathistory', 'chathistory', + 'draft/event-playback' + ]; + const toReq = desired.filter(function (c) { return availableCaps.indexOf(c) !== -1; }); + if (toReq.length) send('CAP REQ :' + toReq.join(' ')); + else send('CAP END'); + } else if (sub === 'ACK') { + const acked = (p[p.length - 1] || '').split(' ').filter(Boolean); + enabledCaps = enabledCaps.concat(acked); + send('CAP END'); + } else if (sub === 'NAK') { + send('CAP END'); + } + break; + } + + case 'BATCH': { + const ref = p[0]; + if (ref && ref[0] === '+') { + const id = ref.slice(1); + batches[id] = { type: p[1] || '', target: p[2] || null, messages: [] }; + } else if (ref && ref[0] === '-') { + const id = ref.slice(1); + if (batches[id]) { + const batch = batches[id]; + delete batches[id]; + batch.messages.forEach(function (m) { handleMessage(m); }); + } + } + break; + } + + case '001': + registered = true; + nick = p[0] || nick; + addMessage('Status', chatNick('***', '#0f0') + 'Connected as ' + esc(nick) + '', timestamp); + updateInputNick(); + updateStatusBar(); + break; + + case '002': case '003': case '004': case '005': + case '250': case '251': case '252': case '253': case '254': case '255': + case '265': case '266': + addMessage('Status', chatNick('***', '#888') + '' + formatIRC(p[p.length - 1] || '') + '', timestamp); + break; + + case '375': case '372': + addMessage('Status', chatNick('***', '#888') + formatIRC(p[p.length - 1] || ''), timestamp); + break; + + case '376': + addMessage('Status', chatNick('***', '#888') + formatIRC(p[p.length - 1] || ''), timestamp); + // Auto-join channels after end of MOTD + setTimeout(function () { + if (registered) { + send('JOIN ' + AUTO_JOIN.join(',')); + // Focus the designated channel once it's joined + const waitForFocus = setInterval(function () { + if (windows[AUTO_JOIN_FOCUS]) { + switchWindow(AUTO_JOIN_FOCUS); + clearInterval(waitForFocus); + } + }, 200); + // Safety: stop waiting after 15 seconds + setTimeout(function () { clearInterval(waitForFocus); }, 15000); + } + }, 3000); + break; + + case '324': { + const chan = p[1]; + const modeStr = p[2] || ''; + const clean = modeStr.replace(/^\+/, ''); + channelModes[chan] = clean; + if (chan === activeWindow) updateTopicBar(); + break; + } + + case '329': + break; + + case '332': { + const chan = p[1]; + if (!windows[chan]) createWindow(chan); + channelTopics[chan] = p[2] || ''; + addMessage(chan, chatNick('***', '#888') + 'Topic: ' + formatIRC(p[2] || '') + '', timestamp); + if (chan === activeWindow) updateTopicBar(); + break; + } + + case '333': + break; + + case '353': { + const chan = p[2]; + if (!windows[chan]) createWindow(chan); + const names = (p[3] || '').split(' ').filter(Boolean); + windows[chan].nicks = windows[chan].nicks.concat(names); + const seen = {}; + windows[chan].nicks = windows[chan].nicks.filter(function (n) { + const bare = n.replace(/^[~&@%+]+/, ''); + if (seen[bare]) return false; + seen[bare] = true; + return true; + }); + break; + } + + case '366': { + const chan = p[1]; + if (chan === activeWindow) { + renderNickList(); + updateInputNick(); + updateTopicBar(); + } + send('MODE ' + chan); + if (!historyLoaded[chan]) { + historyLoaded[chan] = true; + if (enabledCaps.indexOf('draft/chathistory') !== -1 || enabledCaps.indexOf('chathistory') !== -1) { + send('CHATHISTORY LATEST ' + chan + ' * 50'); + } + } + break; + } + + case 'JOIN': { + const chan = p[0].split(' ')[0]; + + if (fromNick === nick && chan.toLowerCase() === BLACKHOLE) { + send('PART ' + chan); + break; + } + + if (fromNick === nick) { + createWindow(chan); + switchWindow(chan); + } else if (windows[chan]) { + if (windows[chan].nicks.indexOf(fromNick) === -1) { + windows[chan].nicks.push(fromNick); + } + } + if (windows[chan]) { + addMessage(chan, chatNick('-->', '#555') + '' + esc(fromNick) + ' has joined', timestamp); + } + if (chan === activeWindow) { + renderNickList(); + updateTopicBar(); + } + break; + } + + case 'PART': { + const chan = p[0]; + const reason = p[1] || ''; + if (fromNick === nick) { + delete windows[chan]; + delete channelModes[chan]; + delete channelTopics[chan]; + delete historyLoaded[chan]; + if (activeWindow === chan) switchWindow('Status'); + renderChannelList(); + } else if (windows[chan]) { + windows[chan].nicks = windows[chan].nicks.filter(function (n) { + return n.replace(/^[~&@%+]+/, '') !== fromNick; + }); + addMessage(chan, chatNick('<--', '#555') + '' + esc(fromNick) + ' has left' + + (reason ? ' (' + esc(reason) + ')' : '') + '', timestamp); + if (chan === activeWindow) { + renderNickList(); + updateTopicBar(); + } + } + break; + } + + case 'QUIT': { + const reason = p[0] || ''; + for (const chan in windows) { + const idx = windows[chan].nicks.findIndex(function (n) { + return n.replace(/^[~&@%+]+/, '') === fromNick; + }); + if (idx !== -1) { + windows[chan].nicks.splice(idx, 1); + addMessage(chan, chatNick('<--', '#555') + '' + esc(fromNick) + ' has quit' + + (reason ? ' (' + esc(reason) + ')' : '') + '', timestamp); + if (chan === activeWindow) { + renderNickList(); + updateTopicBar(); + } + } + } + break; + } + + case 'KICK': { + const chan = p[0]; + const kicked = p[1]; + const reason = p[2] || ''; + if (windows[chan]) { + windows[chan].nicks = windows[chan].nicks.filter(function (n) { + return n.replace(/^[~&@%+]+/, '') !== kicked; + }); + addMessage(chan, chatNick('<--', '#c00') + '' + esc(kicked) + ' was kicked by ' + + esc(fromNick) + (reason ? ' (' + formatIRC(reason) + ')' : '') + '', timestamp); + if (kicked === nick) { + delete windows[chan]; + delete channelModes[chan]; + delete channelTopics[chan]; + delete historyLoaded[chan]; + if (activeWindow === chan) switchWindow('Status'); + renderChannelList(); + } else if (chan === activeWindow) { + renderNickList(); + updateTopicBar(); + } + } + break; + } + + case 'NICK': { + const newNick = p[0]; + const wasMe = fromNick === nick; + if (wasMe) nick = newNick; + for (const chan in windows) { + const idx = windows[chan].nicks.findIndex(function (n) { + return n.replace(/^[~&@%+]+/, '') === fromNick; + }); + if (idx !== -1) { + const old = windows[chan].nicks[idx]; + const bare = old.replace(/^[~&@%+]+/, ''); + const pfx = old.slice(0, old.length - bare.length); + windows[chan].nicks[idx] = pfx + newNick; + addMessage(chan, chatNick('--', '#888') + '' + esc(fromNick) + + ' is now known as ' + esc(newNick) + '', timestamp); + } + } + if (windows[fromNick]) { + windows[newNick] = windows[fromNick]; + delete windows[fromNick]; + if (activeWindow === fromNick) activeWindow = newNick; + renderChannelList(); + } + if (activeWindow && windows[activeWindow]) renderNickList(); + if (wasMe) { + updateInputNick(); + updateStatusBar(); + } + break; + } + + case 'MODE': { + const target = p[0]; + const modeStr = p.slice(1).join(' '); + if (target[0] === '#' || target[0] === '&') { + addMessage(target, chatNick('--', '#888') + '' + esc(fromNick) + + ' sets mode ' + esc(modeStr) + '', timestamp); + if (windows[target]) windows[target].nicks = []; + send('NAMES ' + target); + send('MODE ' + target); + } else { + addMessage('Status', chatNick('--', '#888') + 'Mode ' + esc(modeStr) + '', timestamp); + } + break; + } + + case 'TOPIC': { + const chan = p[0]; + channelTopics[chan] = p[1] || ''; + if (windows[chan]) { + addMessage(chan, chatNick('--', '#888') + '' + esc(fromNick) + + ' changed topic to: ' + formatIRC(p[1] || '') + '', timestamp); + } + if (chan === activeWindow) updateTopicBar(); + break; + } + + case 'PRIVMSG': { + const target = p[0]; + const text = p[1] || ''; + const isAction = text.indexOf('\x01ACTION ') === 0 && text[text.length - 1] === '\x01'; + const isChan = target[0] === '#' || target[0] === '&'; + const wn = isChan ? target : fromNick; + + if (!windows[wn]) createWindow(wn); + + if (isChan) { + const plain = stripIRC(text).toLowerCase(); + if (plain.indexOf(nick.toLowerCase()) !== -1) { + if (wn !== activeWindow) windows[wn].mentioned = true; + playNotificationSound(); + sendDesktopNotification(fromNick + ' in ' + wn, stripIRC(text)); + // Log to Hilights window + const pfx = getNickPrefix(wn, fromNick); + const nc = getNickColor(fromNick); + const hlText = isAction ? text.slice(8, -1) : text; + const hlNick = isAction + ? '* ' + esc(pfx + fromNick) + ' ' + : '<' + esc(pfx + fromNick) + '> '; + addMessage('Hilights', '' + esc(wn) + ' \u2502 ' + hlNick + formatIRC(hlText), timestamp); + } + } + + const nc = getNickColor(fromNick); + if (isAction) { + const at = text.slice(8, -1); + addMessage(wn, chatNick('*', nc) + '' + esc(fromNick) + ' ' + formatIRC(at) + '', timestamp); + } else { + addMessage(wn, chatNick(fromNick, nc) + formatIRC(text), timestamp); + } + break; + } + + case 'NOTICE': { + const target = p[0]; + const text = p[1] || ''; + const isServer = !msg.prefix.includes('!'); + const isChan = target[0] === '#' || target[0] === '&'; + + if (isServer) { + addMessage('Status', chatNick(fromNick || '***', '#ff0') + '' + formatIRC(text) + '', timestamp); + } else if (isChan) { + if (!windows[target]) createWindow(target); + addMessage(target, chatNick(fromNick, '#ff0') + '' + formatIRC(text) + '', timestamp); + } else { + const wn = windows[fromNick] ? fromNick : 'Status'; + addMessage(wn, chatNick(fromNick, '#ff0') + '' + formatIRC(text) + '', timestamp); + } + break; + } + + case 'ERROR': + addMessage('Status', chatNick('!!!', '#f00') + '' + formatIRC(p[0] || '') + '', timestamp); + break; + + case '433': + nick = nick + '_'; + send('NICK ' + nick); + addMessage('Status', chatNick('!!!', '#f00') + 'Nick in use, trying ' + esc(nick) + '', timestamp); + updateInputNick(); + break; + + case '401': case '402': case '403': case '404': case '405': + case '421': case '432': case '441': case '442': + case '461': case '462': case '471': case '473': case '474': case '475': + addMessage(activeWindow, chatNick('!!!', '#f00') + '' + esc(p.slice(1).join(' ')) + '', timestamp); + break; + + default: + if (/^\d{3}$/.test(command)) { + addMessage('Status', chatNick('***', '#888') + '' + esc(p.slice(1).join(' ')) + '', timestamp); + } + break; + } + } + + // ============================================================ + // Connection + // ============================================================ + // --- Get a nick's prefix in a channel --- + function getNickPrefix(chan, target) { + const win = windows[chan]; + if (!win || !win.nicks.length) return ''; + for (let i = 0; i < win.nicks.length; i++) { + const bare = win.nicks[i].replace(/^[~&@%+]+/, ''); + if (bare === target) { + return win.nicks[i].slice(0, win.nicks[i].length - bare.length); + } + } + return ''; + } + + function connect() { + createWindow('Status'); + createWindow('Hilights'); + switchWindow('Status'); + addMessage('Status', chatNick('***', '#888') + 'Connecting to ' + esc(SERVER_URL) + ' ...'); + + ws = new WebSocket(SERVER_URL); + + ws.onopen = function () { + addMessage('Status', chatNick('***', '#0f0') + 'WebSocket connected, negotiating...'); + send('CAP LS 302'); + send('NICK ' + nick); + send('USER webirc 0 * :https://webchat.supernets.org'); + }; + + ws.onmessage = function (event) { + const lines = event.data.split(/\r?\n/).filter(Boolean); + lines.forEach(function (line) { + handleMessage(parseMessage(line)); + }); + }; + + ws.onclose = function () { + addMessage('Status', chatNick('!!!', '#f00') + 'Disconnected.'); + registered = false; + updateStatusBar(); + }; + + ws.onerror = function () { + addMessage('Status', chatNick('!!!', '#f00') + 'WebSocket error.'); + }; + } + + // ============================================================ + // Login + // ============================================================ + function doLogin() { + let n = loginNickEl.value.trim().replace(/[^a-zA-Z0-9_\-\[\]\\^{}|`]/g, '').substring(0, 16); + if (!n) n = 'WebUser' + Math.floor(Math.random() * 99999); + nick = n; + loginEl.classList.add('hidden'); + appEl.classList.remove('hidden'); + requestNotificationPermission(); + inputEl.focus(); + connect(); + } + + const params = new URLSearchParams(window.location.search); + const urlNick = params.get('nick'); + if (urlNick && urlNick.trim()) { + nick = urlNick.trim().replace(/[^a-zA-Z0-9_\-\[\]\\^{}|`]/g, '').substring(0, 16); + if (nick) { + loginEl.classList.add('hidden'); + appEl.classList.remove('hidden'); + requestNotificationPermission(); + connect(); + } + } + + loginBtnEl.addEventListener('click', doLogin); + loginNickEl.addEventListener('keydown', function (e) { + if (e.key === 'Enter') doLogin(); + }); + + // ============================================================ + // Toggle buttons + // ============================================================ + // Set initial toggle state + toggleChanBtn.classList.add('active'); + toggleNickBtn.classList.add('active'); + + toggleChanBtn.addEventListener('click', function () { + showChanlist = !showChanlist; + toggleChanBtn.classList.toggle('active', showChanlist); + updateChanlistVisibility(); + }); + + toggleNickBtn.addEventListener('click', function () { + showNicklist = !showNicklist; + toggleNickBtn.classList.toggle('active', showNicklist); + updateNicklistVisibility(); + }); + + // ============================================================ + // Input handling + // ============================================================ + inputEl.addEventListener('keydown', function (e) { + if (e.key === 'ArrowUp') { + if (commandHistory.length) { + if (historyIndex < commandHistory.length - 1) historyIndex++; + inputEl.value = commandHistory[historyIndex]; + } + e.preventDefault(); + return; + } + if (e.key === 'ArrowDown') { + if (historyIndex > 0) { + historyIndex--; + inputEl.value = commandHistory[historyIndex]; + } else { + historyIndex = -1; + inputEl.value = ''; + } + e.preventDefault(); + return; + } + if (e.key === 'Tab') { + e.preventDefault(); + tabComplete(); + return; + } + if (e.key !== 'Enter') return; + + const text = inputEl.value; + inputEl.value = ''; + historyIndex = -1; + if (!text) return; + + commandHistory.unshift(text); + if (commandHistory.length > 100) commandHistory.pop(); + + if (text[0] === '/') { + const spaceIdx = text.indexOf(' '); + const cmd = (spaceIdx === -1 ? text.substring(1) : text.substring(1, spaceIdx)).toLowerCase(); + const argStr = spaceIdx === -1 ? '' : text.substring(spaceIdx + 1); + const args = argStr ? argStr.split(' ') : []; + + switch (cmd) { + case 'join': + if (args[0]) send('JOIN ' + args[0] + (args[1] ? ' ' + args[1] : '')); + break; + + case 'part': { + const chan = args[0] || (activeWindow !== 'Status' ? activeWindow : ''); + if (chan) send('PART ' + chan + (args.length > 1 ? ' :' + args.slice(1).join(' ') : '')); + break; + } + + case 'msg': case 'privmsg': { + if (args.length >= 2) { + const tgt = args[0]; + const m = args.slice(1).join(' '); + send('PRIVMSG ' + tgt + ' :' + m); + if (!windows[tgt]) createWindow(tgt); + addMessage(tgt, chatNick(nick, getNickColor(nick)) + formatIRC(m)); + } + break; + } + + case 'notice': { + if (args.length >= 2) { + const tgt = args[0]; + const m = args.slice(1).join(' '); + send('NOTICE ' + tgt + ' :' + m); + addMessage(activeWindow, chatNick(nick, '#ff0') + '\u2192 ' + esc(tgt) + ': ' + formatIRC(m) + ''); + } + break; + } + + case 'nick': + if (args[0]) send('NICK ' + args[0]); + break; + + case 'quit': + send('QUIT :' + (argStr || 'Leaving')); + break; + + case 'me': + if (activeWindow !== 'Status') { + send('PRIVMSG ' + activeWindow + ' :\x01ACTION ' + argStr + '\x01'); + const nc = getNickColor(nick); + addMessage(activeWindow, chatNick('*', nc) + '' + esc(nick) + ' ' + formatIRC(argStr) + ''); + } + break; + + case 'topic': + if (activeWindow !== 'Status') { + if (argStr) send('TOPIC ' + activeWindow + ' :' + argStr); + else send('TOPIC ' + activeWindow); + } + break; + + case 'query': + if (args[0]) { + if (!windows[args[0]]) createWindow(args[0]); + switchWindow(args[0]); + } + break; + + case 'close': + if (activeWindow !== 'Status' && activeWindow !== 'Hilights') { + const w = activeWindow; + if (w[0] === '#' || w[0] === '&') send('PART ' + w); + delete windows[w]; + delete channelModes[w]; + delete channelTopics[w]; + delete historyLoaded[w]; + switchWindow('Status'); + renderChannelList(); + } + break; + + case 'clear': + if (windows[activeWindow]) { + windows[activeWindow].messages = []; + renderMessages(); + } + break; + + case 'raw': case 'quote': + if (argStr) send(argStr); + break; + + default: + send(text.substring(1)); + break; + } + } else { + if (activeWindow !== 'Status') { + send('PRIVMSG ' + activeWindow + ' :' + text); + addMessage(activeWindow, chatNick(nick, getNickColor(nick)) + formatIRC(text)); + } + } + }); + + // --- Tab completion for nicks --- + function tabComplete() { + const val = inputEl.value; + const cursorPos = inputEl.selectionStart; + const before = val.substring(0, cursorPos); + const after = val.substring(cursorPos); + const words = before.split(' '); + const partial = words[words.length - 1].toLowerCase(); + if (!partial) return; + + const win = windows[activeWindow]; + if (!win || !win.nicks.length) return; + + const match = win.nicks.find(function (n) { + return n.replace(/^[~&@%+]+/, '').toLowerCase().indexOf(partial) === 0; + }); + if (match) { + const bare = match.replace(/^[~&@%+]+/, ''); + words[words.length - 1] = bare + (words.length === 1 ? ': ' : ' '); + inputEl.value = words.join(' ') + after; + } + } + +})(); diff --git a/style.css b/style.css new file mode 100644 index 0000000..49f8b29 --- /dev/null +++ b/style.css @@ -0,0 +1,400 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +::-webkit-scrollbar { + width: 6px; +} + +::-webkit-scrollbar-track { + background: #0a0a0a; +} + +::-webkit-scrollbar-thumb { + background: #333; +} + +::-webkit-scrollbar-thumb:hover { + background: #555; +} + +body { + background: #000; + color: #c0c0c0; + font-family: 'Courier New', 'Lucida Console', monospace; + font-size: 14px; + height: 100vh; + height: 100dvh; + overflow: hidden; + -webkit-text-size-adjust: 100%; +} + +.hidden { + display: none !important; +} + +/* --- Login screen --- */ +#login { + display: flex; + align-items: center; + justify-content: center; + height: 100vh; + height: 100dvh; + background: #000; +} + +#login-box { + display: flex; + flex-direction: column; + align-items: center; + gap: 14px; + padding: 20px; +} + +#login-logo { + max-width: 200px; + max-height: 200px; + margin-bottom: 4px; + filter: drop-shadow(0 0 8px rgba(0, 255, 255, 0.4)) drop-shadow(0 0 20px rgba(0, 136, 255, 0.2)); + transition: filter 0.3s ease; +} + +#login-logo:hover { + filter: drop-shadow(0 0 12px rgba(0, 255, 255, 0.7)) drop-shadow(0 0 30px rgba(0, 136, 255, 0.4)) drop-shadow(0 0 50px rgba(0, 255, 255, 0.25)); +} + +#login-title { + font-size: 26px; + color: #fff; + letter-spacing: 6px; + text-transform: uppercase; + text-shadow: + 0 0 7px rgba(0, 255, 255, 0.6), + 0 0 14px rgba(0, 255, 255, 0.4), + 0 0 28px rgba(0, 255, 255, 0.2), + 0 0 56px rgba(0, 136, 255, 0.15); +} + +#login-nick { + background: #111; + color: #c0c0c0; + border: 1px solid #333; + padding: 10px 12px; + font-family: 'Courier New', 'Lucida Console', monospace; + font-size: 16px; + outline: none; + width: 260px; + text-align: center; +} + +#login-nick:focus { + border-color: #666; +} + +#login-btn { + background: #1a1a1a; + color: #888; + border: 1px solid #333; + padding: 8px 24px; + font-family: 'Courier New', 'Lucida Console', monospace; + font-size: 14px; + cursor: pointer; + -webkit-tap-highlight-color: transparent; +} + +#login-btn:hover { + background: #222; + color: #ccc; +} + +/* --- App layout --- */ +#app { + display: flex; + flex-direction: column; + height: 100vh; + height: 100dvh; +} + +#main { + display: flex; + flex: 1; + overflow: hidden; + min-height: 0; +} + +/* --- Channel list --- */ +#channels { + width: 140px; + min-width: 140px; + background: #0a0a0a; + border-right: 1px solid #333; + overflow-y: auto; + -webkit-overflow-scrolling: touch; +} + +#channels .tab { + padding: 6px 8px; + cursor: pointer; + color: #888; + border-bottom: 1px solid #1a1a1a; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + font-family: 'Courier New', 'Lucida Console', monospace; + font-size: 13px; + -webkit-tap-highlight-color: transparent; +} + +#channels .tab:hover { + background: #151515; + color: #ccc; +} + +#channels .tab.active { + background: #1a1a1a; + color: #fff; +} + +#channels .tab.unread { + color: #0ff; +} + +#channels .tab.mentioned { + color: #ff0; +} + +/* --- Chat area --- */ +#chat-area { + flex: 1; + display: flex; + flex-direction: column; + overflow: hidden; + min-width: 0; +} + +/* --- Topic bar --- */ +#topicbar { + background: #0a0a0a; + border-bottom: 1px solid #333; + padding: 3px 8px; + font-size: 12px; + color: #999; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + flex-shrink: 0; + min-height: 0; + cursor: default; +} + +#topicbar .topic-channel { + color: #fff; + font-weight: bold; +} + +#topicbar .topic-meta { + color: #666; +} + +#messages { + flex: 1; + overflow-y: auto; + padding: 2px 4px; + -webkit-overflow-scrolling: touch; +} + +#messages .line { + line-height: 1; + margin: 0; + padding: 0; + white-space: pre-wrap; + word-wrap: break-word; +} + +.timestamp { + color: #555; +} + +.nick-col { + display: inline-block; + width: 12ch; + text-align: right; + white-space: nowrap; + overflow: hidden; +} + +.sep { + color: #333; +} + +/* --- Status bar --- */ +#statusbar { + display: flex; + align-items: center; + background: #0a0a0a; + border-top: 1px solid #333; + padding: 2px 6px; + font-size: 12px; + color: #888; + height: 24px; + min-height: 24px; +} + +#statusbar-link { + flex: 1; + display: flex; + align-items: center; + justify-content: center; + color: #555; + text-decoration: none; + -webkit-tap-highlight-color: transparent; +} + +#statusbar-link:hover { + color: #ccc; +} + +#toggle-chanlist, #toggle-nicklist { + color: #555; + cursor: pointer; + -webkit-tap-highlight-color: transparent; + flex-shrink: 0; + display: flex; + align-items: center; + padding: 2px; +} + +#toggle-chanlist:hover, #toggle-nicklist:hover { + color: #fff; +} + +#toggle-chanlist.active, #toggle-nicklist.active { + color: #fff; +} + +#toggle-chanlist svg, #toggle-nicklist svg { + display: block; +} + +/* --- Input row --- */ +#input-row { + display: flex; + align-items: center; + border-top: 1px solid #222; + background: #111; +} + +#input-nick { + padding: 0 4px 0 6px; + color: #888; + white-space: nowrap; + font-family: 'Courier New', 'Lucida Console', monospace; + font-size: 14px; + flex-shrink: 0; +} + +#input { + flex: 1; + min-width: 0; + background: #111; + color: #c0c0c0; + border: none; + padding: 6px 6px; + font-family: 'Courier New', 'Lucida Console', monospace; + font-size: 16px; + outline: none; + -webkit-appearance: none; + appearance: none; + border-radius: 0; +} + +/* --- Nick list --- */ +#nicklist { + width: 140px; + min-width: 140px; + background: #0a0a0a; + border-left: 1px solid #333; + overflow-y: auto; + padding: 0; + -webkit-overflow-scrolling: touch; +} + +#nicklist .nick-category { + padding: 4px 6px 2px; + font-size: 11px; + color: #555; + border-bottom: 1px solid #1a1a1a; + margin-top: 2px; +} + +#nicklist .nick-category:first-child { + margin-top: 0; +} + +#nicklist .nick { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + font-size: 13px; + line-height: 1.4; + padding: 0 6px; + color: #c0c0c0; + cursor: default; +} + +/* --- Mobile --- */ +@media (max-width: 600px) { + body { + font-size: 13px; + } + + #login-logo { + max-width: 140px; + max-height: 140px; + filter: drop-shadow(0 0 6px rgba(0, 255, 255, 0.4)) drop-shadow(0 0 16px rgba(0, 136, 255, 0.2)); + } + + #login-title { + font-size: 18px; + letter-spacing: 4px; + } + + #login-nick { + width: 220px; + } + + #channels { + width: 120px; + min-width: 120px; + } + + #nicklist { + width: 120px; + min-width: 120px; + } + + #input { + font-size: 16px; + } + + #input-nick { + font-size: 13px; + } + + #channels .tab { + padding: 8px 6px; + font-size: 12px; + } + + #nicklist .nick { + font-size: 12px; + padding: 1px 4px; + } + + #topicbar { + font-size: 11px; + padding: 2px 6px; + } +}